agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 4/5] Catcache pruning feature.
527+ messages / 16 participants
[nested] [flat]

* [PATCH 4/5] Catcache pruning feature.
@ 2019-07-01 02:31 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Kyotaro Horiguchi @ 2019-07-01 02:31 UTC (permalink / raw)

Currently we don't have a mechanism to limit the memory amount for
syscache. Syscache bloat often causes process die by OOM killer or
other problems. This patch lets old syscache entries removed to
eventually limit the amount of cache.

This patch intentionally unchanges indentation of an existing code
block in SearchCatCacheInternal for the patch size to be smaller. It
is adjusted in the next patch.
---
 src/backend/utils/cache/catcache.c | 186 +++++++++++++++++++++++++++++++++++++
 src/backend/utils/misc/guc.c       |  12 +++
 src/include/utils/catcache.h       |  17 ++++
 3 files changed, 215 insertions(+)

diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 98427b67cd..b552ae960c 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -60,9 +60,18 @@
 #define CACHE_elog(...)
 #endif
 
+/*
+ * GUC variable to define the minimum age of entries that will be considered
+ * to be evicted in seconds. -1 to disable the feature.
+ */
+int catalog_cache_prune_min_age = -1;
+
 /* Cache management header --- pointer is NULL until created */
 static CatCacheHeader *CacheHdr = NULL;
 
+/* Clock for the last accessed time of a catcache entry. */
+TimestampTz	catcacheclock = 0;
+
 static HeapTuple SearchCatCacheInternal(CatCache *cache,
 										int nkeys,
 										Datum v1, Datum v2,
@@ -864,9 +873,107 @@ InitCatCache(int id,
 	 */
 	MemoryContextSwitchTo(oldcxt);
 
+	/* initialize catcache reference clock if haven't done yet */
+	if (catcacheclock == 0)
+		catcacheclock = GetCurrentTimestamp();
+
+	/*
+	 * This cache doesn't contain a tuple older than the current time. Prevent
+	 * the first pruning from happening too early.
+	 */
+	cp->cc_oldest_ts = catcacheclock;
+
 	return cp;
 }
 
+/*
+ * CatCacheCleanupOldEntries - Remove infrequently-used entries
+ *
+ * Catcache entries happen to be left unused for a long time for several
+ * reasons. Remove such entries to prevent catcache from bloating. It is based
+ * on the similar algorithm with buffer eviction. Entries that are accessed
+ * several times in a certain period live longer than those that have had less
+ * access in the same duration.
+ */
+static bool
+CatCacheCleanupOldEntries(CatCache *cp)
+{
+	int		nremoved = 0;
+	int		i;
+	long	oldest_ts = catcacheclock;
+	long	age;
+	int		us;
+
+	/* Return immediately if disabled */
+	if (catalog_cache_prune_min_age < 0)
+		return false;
+
+	/* Don't scan the hash when we know we don't have prunable entries */
+	TimestampDifference(cp->cc_oldest_ts, catcacheclock, &age, &us);
+	if (age < catalog_cache_prune_min_age)
+		return false;
+
+	/* Scan over the whole hash to find entries to remove */
+	for (i = 0 ; i < cp->cc_nbuckets ; i++)
+	{
+		dlist_mutable_iter	iter;
+
+		dlist_foreach_modify(iter, &cp->cc_bucket[i])
+		{
+			CatCTup    *ct = dlist_container(CatCTup, cache_elem, iter.cur);
+
+			/* Don't remove referenced entries */
+			if (ct->refcount == 0 &&
+				(ct->c_list == NULL || ct->c_list->refcount == 0))
+			{
+				/*
+				 * Calculate the duration from the time from the last access
+				 * to the "current" time. catcacheclock is updated
+				 * per-statement basis and additionaly udpated periodically
+				 * during a long running query.
+				 */
+				TimestampDifference(ct->lastaccess, catcacheclock, &age, &us);
+
+				if (age > catalog_cache_prune_min_age)
+				{
+					/*
+					 * Entries that are not accessed after the last pruning
+					 * are removed in that seconds, and their lives are
+					 * prolonged according to how many times they are accessed
+					 * up to three times of the duration. We don't try shrink
+					 * buckets since pruning effectively caps catcache
+					 * expansion in the long term.
+					 */
+					if (ct->naccess > 2)
+						ct->naccess = 1;
+					else if (ct->naccess > 0)
+						ct->naccess--;
+					else
+					{
+						CatCacheRemoveCTup(cp, ct);
+						nremoved++;
+
+						/* don't update oldest_ts by removed entry */
+						continue;
+					}
+				}
+			}
+
+			/* update oldest timestamp if the entry remains alive */
+			if (ct->lastaccess < oldest_ts)
+				oldest_ts = ct->lastaccess;
+		}
+	}
+
+	cp->cc_oldest_ts = oldest_ts;
+
+	if (nremoved > 0)
+		elog(DEBUG1, "pruning catalog cache id=%d for %s: removed %d / %d",
+			 cp->id, cp->cc_relname, nremoved, cp->cc_ntup + nremoved);
+
+	return nremoved > 0;
+}
+
 /*
  * Enlarge a catcache, doubling the number of buckets.
  */
@@ -880,6 +987,10 @@ RehashCatCache(CatCache *cp)
 	elog(DEBUG1, "rehashing catalog cache id %d for %s; %d tups, %d buckets",
 		 cp->id, cp->cc_relname, cp->cc_ntup, cp->cc_nbuckets);
 
+	/* try removing old entries before expanding the hash */
+	if (CatCacheCleanupOldEntries(cp))
+		return;
+
 	/* Allocate a new, larger, hash table. */
 	newnbuckets = cp->cc_nbuckets * 2;
 	newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head));
@@ -1257,6 +1368,14 @@ SearchCatCacheInternal(CatCache *cache,
 	 * dlist within the loop, because we don't continue the loop afterwards.
 	 */
 	bucket = &cache->cc_bucket[hashIndex];
+
+	/*
+	 * Even though this branch leads to duplicate of a bit much code, we want
+	 * as less branches as possible here to keep fastest when pruning is
+	 * disabled. Don't try to move this branch to foreach to save lines.
+	 */
+	if (likely(catalog_cache_prune_min_age < 0))
+	{
 	dlist_foreach(iter, bucket)
 	{
 		ct = dlist_container(CatCTup, cache_elem, iter.cur);
@@ -1309,6 +1428,71 @@ SearchCatCacheInternal(CatCache *cache,
 			return NULL;
 		}
 	}
+	}
+	else
+	{
+		/*
+		 * We manage the age of each entries for pruning in this branch.
+		 */
+		dlist_foreach(iter, bucket)
+		{
+			/* The following section is the same with the if() block */
+			ct = dlist_container(CatCTup, cache_elem, iter.cur);
+
+			if (ct->dead)
+				continue;
+
+			if (ct->hash_value != hashValue)
+				continue;
+
+			if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments))
+				continue;
+
+			dlist_move_head(bucket, &ct->cache_elem);
+
+			/*
+			 * Prolong life of this entry. Since we want run as less
+			 * instructions as possible and want the branch be stable for
+			 * performance reasons, we don't give a strict cap on the
+			 * counter. All numbers above 1 will be regarded as 2 in
+			 * CatCacheCleanupOldEntries().
+			 */
+			ct->naccess++;
+			if (unlikely(ct->naccess == 0))
+				ct->naccess = 2;
+			ct->lastaccess = catcacheclock;
+
+			/* Following part is also the same with if() block above */
+			if (!ct->negative)
+			{
+				ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+				ct->refcount++;
+				ResourceOwnerRememberCatCacheRef(CurrentResourceOwner,
+												 &ct->tuple);
+
+				CACHE_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d",
+						   cache->cc_relname, hashIndex);
+				
+#ifdef CATCACHE_STATS
+				cache->cc_hits++;
+#endif
+				
+
+				return &ct->tuple;
+			}
+			else
+			{
+				CACHE_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d",
+						   cache->cc_relname, hashIndex);
+
+#ifdef CATCACHE_STATS
+				cache->cc_neg_hits++;
+#endif
+
+				return NULL;
+			}
+		}
+	}
 
 	return SearchCatCacheMiss(cache, nkeys, hashValue, hashIndex, v1, v2, v3, v4);
 }
@@ -1902,6 +2086,8 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
 	ct->dead = false;
 	ct->negative = negative;
 	ct->hash_value = hashValue;
+	ct->naccess = 0;
+	ct->lastaccess = catcacheclock;
 
 	dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem);
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 92c4fee8f8..c2a4caa44b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -82,6 +82,7 @@
 #include "tsearch/ts_cache.h"
 #include "utils/builtins.h"
 #include "utils/bytea.h"
+#include "utils/catcache.h"
 #include "utils/guc_tables.h"
 #include "utils/float.h"
 #include "utils/memutils.h"
@@ -2252,6 +2253,17 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"catalog_cache_prune_min_age", PGC_USERSET, RESOURCES_MEM,
+			gettext_noop("System catalog cache entries that live unused for longer than this seconds are considered for removal."),
+			gettext_noop("The value of -1 turns off pruning."),
+			GUC_UNIT_S
+		},
+		&catalog_cache_prune_min_age,
+		300, -1, INT_MAX,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * We use the hopefully-safely-small value of 100kB as the compiled-in
 	 * default for max_stack_depth.  InitializeGUCOptions will increase it if
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index ff1fabaca1..ad962fb096 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -22,6 +22,7 @@
 
 #include "access/htup.h"
 #include "access/skey.h"
+#include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "utils/relcache.h"
 
@@ -61,6 +62,7 @@ typedef struct catcache
 	slist_node	cc_next;		/* list link */
 	ScanKeyData cc_skey[CATCACHE_MAXKEYS];	/* precomputed key info for heap
 											 * scans */
+	TimestampTz	cc_oldest_ts;	/* timestamp of the oldest tuple in the hash */
 
 	/*
 	 * Keep these at the end, so that compiling catcache.c with CATCACHE_STATS
@@ -119,6 +121,8 @@ typedef struct catctup
 	bool		dead;			/* dead but not yet removed? */
 	bool		negative;		/* negative cache entry? */
 	HeapTupleData tuple;		/* tuple management header */
+	unsigned int naccess;		/* # of access to this entry */
+	TimestampTz	lastaccess;		/* timestamp of the last usage */
 
 	/*
 	 * The tuple may also be a member of at most one CatCList.  (If a single
@@ -189,6 +193,19 @@ typedef struct catcacheheader
 /* this extern duplicates utils/memutils.h... */
 extern PGDLLIMPORT MemoryContext CacheMemoryContext;
 
+/* for guc.c, not PGDLLPMPORT'ed */
+extern int catalog_cache_prune_min_age;
+
+/* source clock for access timestamp of catcache entries */
+extern TimestampTz catcacheclock;
+
+/* SetCatCacheClock - set catcache timestamp source clodk */
+static inline void
+SetCatCacheClock(TimestampTz ts)
+{
+	catcacheclock = ts;
+}
+
 extern void CreateCacheMemoryContext(void);
 
 extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid,
-- 
2.16.3


----Next_Part(Mon_Jul_01_16_02_59_2019_106)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v18-0005-Adjust-indentation-of-SearchCatCacheInternal.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v4 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/backend/storage/file/fd.c         |  4 +-
 src/backend/utils/misc/guc_tables.c   |  7 ++-
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 14 +++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 24 +++++++
 src/include/common/file_utils.h       | 15 ++++-
 src/include/fe_utils/option_utils.h   |  4 ++
 src/include/storage/fd.h              | 10 ++-
 src/tools/pgindent/typedefs.list      |  1 +
 28 files changed, 388 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index a027a8aabc..206db91217 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f9dba43b8c..331f65cbe6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..6d7d10cffb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..adcc767b0f 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -87,6 +88,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -445,6 +447,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +506,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +640,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..895360dac7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..64a7469f22 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..c977c990ad 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..93aa5bdaf8 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,13 +26,22 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..dedb773304 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,15 +43,11 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
@@ -195,6 +191,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 51b7951ad8..07387ed251 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2678,6 +2678,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+DataDirSyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--k+w/mQv8wyuph6w0--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v2 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/bin/initdb/initdb.c               | 11 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 10 ++-
 src/bin/pg_checksums/pg_checksums.c   |  8 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 13 ++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             |  9 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  8 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 12 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 17 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 26 files changed, 369 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..908263ee62 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -3106,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3288,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3339,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3416,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..3443a91956 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -2199,11 +2200,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2282,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2454,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..a1dfc51273 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -445,6 +446,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +505,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +639,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..7a2bb92938 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  SyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..979db4bba6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   SyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  SyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,7 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker, SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2235,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, SyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2289,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..e32e00547b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	SyncMethod	sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..fec1b6b3a9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..a424762f1e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -131,6 +133,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +221,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..4f9da8e685 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..8aa7784b5f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, SyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..3044f7f742 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, SyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ab97f1794a..0635ce4a87 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2678,6 +2678,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--82I3+IH0IqGh5yIs--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v9 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 12 +++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 268 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..45f96cd8bb 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,17 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--2fHTh5uZTiUOsy+g--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v3 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 13 ++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 17 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 26 files changed, 376 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..bfbe5bd3fd 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..51f29f83fd 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..3ddbffd471 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -87,6 +88,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -445,6 +447,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +506,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +640,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..7a2bb92938 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  SyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..979db4bba6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   SyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  SyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,7 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker, SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2235,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, SyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2289,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..e32e00547b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	SyncMethod	sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..b8117fe536 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..25cffb897d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..44675131a2 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..8aa7784b5f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, SyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..3044f7f742 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, SyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 66823bc2a7..bebb2b8e49 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2677,6 +2677,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--sdtB3X0nJg68CQEu--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v3 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 13 ++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 17 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 26 files changed, 376 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..bfbe5bd3fd 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..51f29f83fd 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..3ddbffd471 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -87,6 +88,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -445,6 +447,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +506,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +640,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..7a2bb92938 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  SyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..979db4bba6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   SyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  SyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,7 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker, SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2235,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, SyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2289,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..e32e00547b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	SyncMethod	sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..b8117fe536 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..25cffb897d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..44675131a2 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..8aa7784b5f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, SyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..3044f7f742 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, SyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 66823bc2a7..bebb2b8e49 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2677,6 +2677,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--sdtB3X0nJg68CQEu--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v6 2/2] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +---
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 +++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 ++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 +++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 +++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 +++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++
 src/backend/storage/file/fd.c         |  4 +-
 src/backend/utils/misc/guc_tables.c   |  7 ++-
 src/bin/initdb/initdb.c               | 11 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 14 +++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 24 +++++++
 src/include/common/file_utils.h       | 11 +++-
 src/include/fe_utils/option_utils.h   |  4 ++
 src/include/storage/fd.h              |  6 --
 src/tools/pgindent/typedefs.list      |  1 +
 32 files changed, 389 insertions(+), 40 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index b490a76ba7..3fed475c38 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e565a3092f..5abebe9a9c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 905b979947..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -165,6 +165,7 @@ static bool show_setting = false;
 static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static int	wal_segment_size_mb = (DEFAULT_XLOG_SEG_SIZE) / (1024 * 1024);
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3286,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3333,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3396,7 +3403,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 9011a19b4e..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -77,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -435,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -493,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -623,7 +630,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 65f64c282d..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7f69f02441..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..c977c990ad 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index dd1532bcb0..09d1e5d561 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,10 +26,17 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
 #endif
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index aea30c0622..d9d5d9da5f 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -46,12 +46,6 @@
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 49a33c0387..fe571c6265 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -545,6 +545,7 @@ DR_printtup
 DR_sqlfunction
 DR_transientrel
 DWORD
+DataDirSyncMethod
 DataDumperPtr
 DataPageDeleteStack
 DatabaseInfo
-- 
2.25.1


--bp/iNruPH9dso1Pn--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v3 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 13 ++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 17 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 26 files changed, 376 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..bfbe5bd3fd 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..51f29f83fd 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..3ddbffd471 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -87,6 +88,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -445,6 +447,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +506,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +640,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..7a2bb92938 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  SyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..979db4bba6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   SyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  SyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,7 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker, SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2235,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, SyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2289,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..e32e00547b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	SyncMethod	sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..b8117fe536 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..25cffb897d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..44675131a2 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..8aa7784b5f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, SyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..3044f7f742 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, SyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 66823bc2a7..bebb2b8e49 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2677,6 +2677,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--sdtB3X0nJg68CQEu--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v8 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 14 +++++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 270 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..2f2c3faba0 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,19 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'fsync' ],
+	'sync method fsync');
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--oyUTqETQ0mS9luUI--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v9 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 12 +++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 268 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..45f96cd8bb 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,17 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--2fHTh5uZTiUOsy+g--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v8 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 14 +++++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 270 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..2f2c3faba0 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,19 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'fsync' ],
+	'sync method fsync');
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--oyUTqETQ0mS9luUI--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v6 2/2] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +---
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 +++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 ++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 +++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 +++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 +++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++
 src/backend/storage/file/fd.c         |  4 +-
 src/backend/utils/misc/guc_tables.c   |  7 ++-
 src/bin/initdb/initdb.c               | 11 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 14 +++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 24 +++++++
 src/include/common/file_utils.h       | 11 +++-
 src/include/fe_utils/option_utils.h   |  4 ++
 src/include/storage/fd.h              |  6 --
 src/tools/pgindent/typedefs.list      |  1 +
 32 files changed, 389 insertions(+), 40 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index b490a76ba7..3fed475c38 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e565a3092f..5abebe9a9c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 905b979947..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -165,6 +165,7 @@ static bool show_setting = false;
 static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static int	wal_segment_size_mb = (DEFAULT_XLOG_SEG_SIZE) / (1024 * 1024);
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3286,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3333,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3396,7 +3403,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 9011a19b4e..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -77,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -435,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -493,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -623,7 +630,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 65f64c282d..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7f69f02441..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..c977c990ad 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index dd1532bcb0..09d1e5d561 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,10 +26,17 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
 #endif
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index aea30c0622..d9d5d9da5f 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -46,12 +46,6 @@
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 49a33c0387..fe571c6265 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -545,6 +545,7 @@ DR_printtup
 DR_sqlfunction
 DR_transientrel
 DWORD
+DataDirSyncMethod
 DataDumperPtr
 DataPageDeleteStack
 DatabaseInfo
-- 
2.25.1


--bp/iNruPH9dso1Pn--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v3 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 13 ++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 17 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 26 files changed, 376 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..bfbe5bd3fd 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..51f29f83fd 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..3ddbffd471 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -87,6 +88,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -445,6 +447,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +506,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +640,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..7a2bb92938 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  SyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..979db4bba6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   SyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  SyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,7 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker, SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2235,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, SyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2289,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..e32e00547b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	SyncMethod	sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..b8117fe536 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..25cffb897d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..44675131a2 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..8aa7784b5f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, SyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..3044f7f742 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, SyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 66823bc2a7..bebb2b8e49 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2677,6 +2677,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--sdtB3X0nJg68CQEu--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v9 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 12 +++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 268 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..45f96cd8bb 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,17 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--2fHTh5uZTiUOsy+g--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v1 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 src/bin/initdb/initdb.c               | 11 +++-
 src/bin/pg_basebackup/pg_basebackup.c |  8 ++-
 src/bin/pg_checksums/pg_checksums.c   |  8 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  8 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 12 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 79 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 15 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 15 files changed, 168 insertions(+), 10 deletions(-)

diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..908263ee62 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -3106,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3288,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3339,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3416,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..548e764b2f 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -2203,7 +2204,7 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2282,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2454,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..a1dfc51273 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -445,6 +446,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +505,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +639,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..a424762f1e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -131,6 +133,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +221,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..4f9da8e685 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..b3d814d0c4 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,33 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY | PG_BINARY, 0);
+
+	if (fd < 0)
+	{
+		if (errno == EACCES || errno == EISDIR)
+			return;
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +90,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +117,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..fd5162fef1 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
 extern void fsync_dir_recurse(const char *dir);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 11d47294cf..3bbf70fdf3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2678,6 +2678,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--IJpNTDwzlM2Ie8A6--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v7 2/2] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              |  12 +-
 doc/src/sgml/filelist.sgml            |   1 +
 doc/src/sgml/postgres.sgml            |   1 +
 doc/src/sgml/ref/initdb.sgml          |  22 ++++
 doc/src/sgml/ref/pg_basebackup.sgml   |  25 ++++
 doc/src/sgml/ref/pg_checksums.sgml    |  22 ++++
 doc/src/sgml/ref/pg_dump.sgml         |  21 ++++
 doc/src/sgml/ref/pg_rewind.sgml       |  22 ++++
 doc/src/sgml/ref/pgupgrade.sgml       |  23 ++++
 doc/src/sgml/syncfs.sgml              |  36 ++++++
 src/backend/storage/file/fd.c         |   4 +-
 src/backend/utils/misc/guc_tables.c   |   7 +-
 src/bin/initdb/initdb.c               |  11 +-
 src/bin/pg_basebackup/pg_basebackup.c |  12 +-
 src/bin/pg_checksums/pg_checksums.c   |   9 +-
 src/bin/pg_dump/pg_backup.h           |   4 +-
 src/bin/pg_dump/pg_backup_archiver.c  |  14 ++-
 src/bin/pg_dump/pg_backup_archiver.h  |   1 +
 src/bin/pg_dump/pg_backup_directory.c |   2 +-
 src/bin/pg_dump/pg_dump.c             |  10 +-
 src/bin/pg_rewind/file_ops.c          |   2 +-
 src/bin/pg_rewind/pg_rewind.c         |   9 ++
 src/bin/pg_rewind/pg_rewind.h         |   2 +
 src/bin/pg_upgrade/option.c           |  13 ++
 src/bin/pg_upgrade/pg_upgrade.c       |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |   1 +
 src/common/file_utils.c               | 170 +++++++++++++++++++++-----
 src/fe_utils/option_utils.c           |  24 ++++
 src/include/common/file_utils.h       |  11 +-
 src/include/fe_utils/option_utils.h   |   4 +
 src/include/storage/fd.h              |   6 -
 src/tools/pgindent/typedefs.list      |   1 +
 32 files changed, 441 insertions(+), 67 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index b490a76ba7..3fed475c38 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e565a3092f..5abebe9a9c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 905b979947..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -165,6 +165,7 @@ static bool show_setting = false;
 static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static int	wal_segment_size_mb = (DEFAULT_XLOG_SEG_SIZE) / (1024 * 1024);
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3286,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3333,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3396,7 +3403,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 9011a19b4e..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -77,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -435,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -493,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -623,7 +630,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 65f64c282d..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7f69f02441..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..05c73c0bb7 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,30 +115,93 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
-	/*
-	 * If possible, hint to the kernel that we're soon going to fsync the data
-	 * directory and its contents.
-	 */
+	switch (sync_method)
+	{
+		case DATA_DIR_SYNC_METHOD_SYNCFS:
+			{
+#ifndef HAVE_SYNCFS
+				pg_log_error("this build does not support sync method \"%s\"",
+							 "syncfs");
+				exit(EXIT_FAILURE);
+#else
+				DIR		   *dir;
+				struct dirent *de;
+
+				/*
+				 * On Linux, we don't have to open every single file one by
+				 * one.  We can use syncfs() to sync whole filesystems.  We
+				 * only expect filesystem boundaries to exist where we
+				 * tolerate symlinks, namely pg_wal and the tablespaces, so we
+				 * call syncfs() for each of those directories.
+				 */
+
+				/* Sync the top level pgdata directory. */
+				do_syncfs(pg_data);
+
+				/* If any tablespaces are configured, sync each of those. */
+				dir = opendir(pg_tblspc);
+				if (dir == NULL)
+					pg_log_error("could not open directory \"%s\": %m",
+								 pg_tblspc);
+				else
+				{
+					while (errno = 0, (de = readdir(dir)) != NULL)
+					{
+						char		subpath[MAXPGPATH * 2];
+
+						if (strcmp(de->d_name, ".") == 0 ||
+							strcmp(de->d_name, "..") == 0)
+							continue;
+
+						snprintf(subpath, sizeof(subpath), "%s/%s",
+								 pg_tblspc, de->d_name);
+						do_syncfs(subpath);
+					}
+
+					if (errno)
+						pg_log_error("could not read directory \"%s\": %m",
+									 pg_tblspc);
+
+					(void) closedir(dir);
+				}
+
+				/* If pg_wal is a symlink, process that too. */
+				if (xlog_is_symlink)
+					do_syncfs(pg_wal);
+#endif							/* HAVE_SYNCFS */
+			}
+			break;
+
+		case DATA_DIR_SYNC_METHOD_FSYNC:
+			{
+				/*
+				 * If possible, hint to the kernel that we're soon going to
+				 * fsync the data directory and its contents.
+				 */
 #ifdef PG_FLUSH_DATA_WORKS
-	walkdir(pg_data, pre_sync_fname, false);
-	if (xlog_is_symlink)
-		walkdir(pg_wal, pre_sync_fname, false);
-	walkdir(pg_tblspc, pre_sync_fname, true);
+				walkdir(pg_data, pre_sync_fname, false);
+				if (xlog_is_symlink)
+					walkdir(pg_wal, pre_sync_fname, false);
+				walkdir(pg_tblspc, pre_sync_fname, true);
 #endif
 
-	/*
-	 * Now we do the fsync()s in the same order.
-	 *
-	 * The main call ignores symlinks, so in addition to specially processing
-	 * pg_wal if it's a symlink, pg_tblspc has to be visited separately with
-	 * process_symlinks = true.  Note that if there are any plain directories
-	 * in pg_tblspc, they'll get fsync'd twice.  That's not an expected case
-	 * so we don't worry about optimizing it.
-	 */
-	walkdir(pg_data, fsync_fname, false);
-	if (xlog_is_symlink)
-		walkdir(pg_wal, fsync_fname, false);
-	walkdir(pg_tblspc, fsync_fname, true);
+				/*
+				 * Now we do the fsync()s in the same order.
+				 *
+				 * The main call ignores symlinks, so in addition to specially
+				 * processing pg_wal if it's a symlink, pg_tblspc has to be
+				 * visited separately with process_symlinks = true.  Note that
+				 * if there are any plain directories in pg_tblspc, they'll
+				 * get fsync'd twice. That's not an expected case so we don't
+				 * worry about optimizing it.
+				 */
+				walkdir(pg_data, fsync_fname, false);
+				if (xlog_is_symlink)
+					walkdir(pg_wal, fsync_fname, false);
+				walkdir(pg_tblspc, fsync_fname, true);
+			}
+			break;
+	}
 }
 
 /*
@@ -121,17 +210,40 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
-	/*
-	 * If possible, hint to the kernel that we're soon going to fsync the data
-	 * directory and its contents.
-	 */
+	switch (sync_method)
+	{
+		case DATA_DIR_SYNC_METHOD_SYNCFS:
+			{
+#ifndef HAVE_SYNCFS
+				pg_log_error("this build does not support sync method \"%s\"",
+							 "syncfs");
+				exit(EXIT_FAILURE);
+#else
+				/*
+				 * On Linux, we don't have to open every single file one by
+				 * one.  We can use syncfs() to sync the whole filesystem.
+				 */
+				do_syncfs(dir);
+#endif							/* HAVE_SYNCFS */
+			}
+			break;
+
+		case DATA_DIR_SYNC_METHOD_FSYNC:
+			{
+				/*
+				 * If possible, hint to the kernel that we're soon going to
+				 * fsync the data directory and its contents.
+				 */
 #ifdef PG_FLUSH_DATA_WORKS
-	walkdir(dir, pre_sync_fname, false);
+				walkdir(dir, pre_sync_fname, false);
 #endif
 
-	walkdir(dir, fsync_fname, false);
+				walkdir(dir, fsync_fname, false);
+			}
+			break;
+	}
 }
 
 /*
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index dd1532bcb0..09d1e5d561 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,10 +26,17 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
 #endif
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index aea30c0622..d9d5d9da5f 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -46,12 +46,6 @@
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 49a33c0387..fe571c6265 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -545,6 +545,7 @@ DR_printtup
 DR_sqlfunction
 DR_transientrel
 DWORD
+DataDirSyncMethod
 DataDumperPtr
 DataPageDeleteStack
 DatabaseInfo
-- 
2.25.1


--3MwIy2ne0vdjdPXF--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v2 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/bin/initdb/initdb.c               | 11 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 10 ++-
 src/bin/pg_checksums/pg_checksums.c   |  8 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 13 ++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             |  9 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  8 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 12 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 18 ++++++
 src/include/common/file_utils.h       | 17 ++++-
 src/include/fe_utils/option_utils.h   |  3 +
 src/include/storage/fd.h              |  4 ++
 src/tools/pgindent/typedefs.list      |  1 +
 26 files changed, 369 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..908263ee62 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -3106,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3288,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3339,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3416,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..3443a91956 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -2199,11 +2200,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2282,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2454,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..a1dfc51273 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static SyncMethod sync_method = SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -445,6 +446,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +505,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +639,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..7a2bb92938 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  SyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..979db4bba6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   SyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  SyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,7 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker, SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2235,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, SyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2289,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..e32e00547b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	SyncMethod	sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..fec1b6b3a9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..a424762f1e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+SyncMethod	sync_method = SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -131,6 +133,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +221,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..a7ce754880 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern SyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..4f9da8e685 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	SyncMethod	unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..8aa7784b5f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 SyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, SyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..c65aedf8f5 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,21 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, SyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = SYNC_METHOD_FSYNC;
+#ifdef HAVE_SYNCFS
+	else if (strcmp(optarg, "syncfs") == 0)
+		*sync_method = SYNC_METHOD_SYNCFS;
+#endif
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..3044f7f742 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -27,12 +27,23 @@ typedef enum PGFileType
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
 #ifdef FRONTEND
+
+typedef enum SyncMethod
+{
+	SYNC_METHOD_FSYNC,
+#ifdef HAVE_SYNCFS
+	SYNC_METHOD_SYNCFS
+#endif
+} SyncMethod;
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 SyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, SyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..3ad8737219 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,6 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg, SyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..196f20e716 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,6 +43,8 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
@@ -195,6 +197,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ab97f1794a..0635ce4a87 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2678,6 +2678,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+SyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--82I3+IH0IqGh5yIs--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v9 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 12 +++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 268 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..45f96cd8bb 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,17 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--2fHTh5uZTiUOsy+g--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v5 2/2] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +---
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 +++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 ++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 +++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 +++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 +++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++
 src/backend/storage/file/fd.c         |  4 +-
 src/backend/utils/misc/guc_tables.c   |  7 ++-
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 14 +++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 24 +++++++
 src/include/common/file_utils.h       | 11 +++-
 src/include/fe_utils/option_utils.h   |  4 ++
 src/include/storage/fd.h              |  6 --
 src/tools/pgindent/typedefs.list      |  1 +
 32 files changed, 390 insertions(+), 40 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 11251fa05e..77b008b854 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index a027a8aabc..206db91217 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f9dba43b8c..331f65cbe6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..6d7d10cffb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 9011a19b4e..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -77,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -435,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -493,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -623,7 +630,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..895360dac7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..64a7469f22 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..c977c990ad 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index dd1532bcb0..09d1e5d561 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,10 +26,17 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
 #endif
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index aea30c0622..d9d5d9da5f 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -46,12 +46,6 @@
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 51b7951ad8..46c916ae24 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -544,6 +544,7 @@ DR_printtup
 DR_sqlfunction
 DR_transientrel
 DWORD
+DataDirSyncMethod
 DataDumperPtr
 DataPageDeleteStack
 DatabaseInfo
-- 
2.25.1


--X1bOJ3K7DJ5YkBrT--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v5 2/2] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +---
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 +++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 ++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 +++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 +++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 +++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++
 src/backend/storage/file/fd.c         |  4 +-
 src/backend/utils/misc/guc_tables.c   |  7 ++-
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 14 +++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 24 +++++++
 src/include/common/file_utils.h       | 11 +++-
 src/include/fe_utils/option_utils.h   |  4 ++
 src/include/storage/fd.h              |  6 --
 src/tools/pgindent/typedefs.list      |  1 +
 32 files changed, 390 insertions(+), 40 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 11251fa05e..77b008b854 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index a027a8aabc..206db91217 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f9dba43b8c..331f65cbe6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..6d7d10cffb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 9011a19b4e..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -77,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -435,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -493,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -623,7 +630,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..895360dac7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..64a7469f22 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..c977c990ad 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index dd1532bcb0..09d1e5d561 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,10 +26,17 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
 #endif
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index aea30c0622..d9d5d9da5f 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -46,12 +46,6 @@
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 51b7951ad8..46c916ae24 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -544,6 +544,7 @@ DR_printtup
 DR_sqlfunction
 DR_transientrel
 DWORD
+DataDirSyncMethod
 DataDumperPtr
 DataPageDeleteStack
 DatabaseInfo
-- 
2.25.1


--X1bOJ3K7DJ5YkBrT--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v9 4/4] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml              | 12 +++------
 doc/src/sgml/filelist.sgml            |  1 +
 doc/src/sgml/postgres.sgml            |  1 +
 doc/src/sgml/ref/initdb.sgml          | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 25 +++++++++++++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 22 ++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 21 ++++++++++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 22 ++++++++++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 23 +++++++++++++++++
 doc/src/sgml/syncfs.sgml              | 36 +++++++++++++++++++++++++++
 src/bin/initdb/initdb.c               |  6 +++++
 src/bin/initdb/t/001_initdb.pl        | 12 +++++++++
 src/bin/pg_basebackup/pg_basebackup.c |  7 ++++++
 src/bin/pg_checksums/pg_checksums.c   |  6 +++++
 src/bin/pg_dump/pg_dump.c             |  7 ++++++
 src/bin/pg_rewind/pg_rewind.c         |  8 ++++++
 src/bin/pg_upgrade/option.c           | 13 ++++++++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +++--
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/fe_utils/option_utils.c           | 24 ++++++++++++++++++
 src/include/fe_utils/option_utils.h   |  4 +++
 21 files changed, 268 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/syncfs.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..2c7d9f1262 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10580,15 +10580,9 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
         On Linux, <literal>syncfs</literal> may be used instead, to ask the
         operating system to synchronize the whole file systems that contain the
         data directory, the WAL files and each tablespace (but not any other
-        file systems that may be reachable through symbolic links).  This may
-        be a lot faster than the <literal>fsync</literal> setting, because it
-        doesn't need to open each file one by one.  On the other hand, it may
-        be slower if a file system is shared by other applications that
-        modify a lot of files, since those files will also be written to disk.
-        Furthermore, on versions of Linux before 5.8, I/O errors encountered
-        while writing data to disk may not be reported to
-        <productname>PostgreSQL</productname>, and relevant error messages may
-        appear only in kernel logs.
+        file systems that may be reachable through symbolic links).  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
        </para>
        <para>
         This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 63b0fc2a46..df5d7c3025 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -184,6 +184,7 @@
 <!ENTITY acronyms   SYSTEM "acronyms.sgml">
 <!ENTITY glossary   SYSTEM "glossary.sgml">
 <!ENTITY color      SYSTEM "color.sgml">
+<!ENTITY syncfs     SYSTEM "syncfs.sgml">
 
 <!ENTITY features-supported   SYSTEM "features-supported.sgml">
 <!ENTITY features-unsupported SYSTEM "features-unsupported.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f629524be0 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -294,6 +294,7 @@ break is not needed in a wider output rendering.
   &acronyms;
   &glossary;
   &color;
+  &syncfs;
   &obsolete;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..8a09c5c438 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..d2b8ddd200 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,31 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..7b44ba71cf 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..c1e2220b3c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,27 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  See <xref linkend="syncfs"/> for more information
+        about using <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..80dff16168 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  See
+        <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..6c033485ea 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,29 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        See <xref linkend="syncfs"/> for more information about using
+        <function>syncfs()</function>.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/doc/src/sgml/syncfs.sgml b/doc/src/sgml/syncfs.sgml
new file mode 100644
index 0000000000..00457d2457
--- /dev/null
+++ b/doc/src/sgml/syncfs.sgml
@@ -0,0 +1,36 @@
+<!-- doc/src/sgml/syncfs.sgml -->
+
+<appendix id="syncfs">
+ <title><function>syncfs()</function> Caveats</title>
+
+ <indexterm zone="syncfs">
+  <primary>syncfs</primary>
+ </indexterm>
+
+ <para>
+  On Linux <function>syncfs()</function> may be specified for some
+  configuration parameters (e.g.,
+  <xref linkend="guc-recovery-init-sync-method"/>), server applications (e.g.,
+  <application>pg_upgrade</application>), and client applications (e.g.,
+  <application>pg_basebackup</application>) that involve synchronizing many
+  files to disk.  <function>syncfs()</function> is advantageous in many cases,
+  but there are some trade-offs to keep in mind.
+ </para>
+
+ <para>
+  Since <function>syncfs()</function> instructs the operating system to
+  synchronize a whole file system, it typically requires many fewer system
+  calls than using <function>fsync()</function> to synchronize each file one by
+  one.  Therefore, using <function>syncfs()</function> may be a lot faster than
+  using <function>fsync()</function>.  However, it may be slower if a file
+  system is shared by other applications that modify a lot of files, since
+  those files will also be written to disk.
+ </para>
+
+ <para>
+  Furthermore, on versions of Linux before 5.8, I/O errors encountered while
+  writing data to disk may not be reported to the calling program, and relevant
+  error messages may appear only in kernel logs.
+ </para>
+
+</appendix>
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbea1e412b..543927b8b4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2467,6 +2467,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3107,6 +3108,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3287,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 2d7469d2fc..45f96cd8bb 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -16,6 +16,7 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
 my $datadir = "$tempdir/data";
+my $supports_syncfs = check_pg_config("#define HAVE_SYNCFS 1");
 
 program_help_ok('initdb');
 program_version_ok('initdb');
@@ -82,6 +83,17 @@ command_fails([ 'pg_checksums', '-D', $datadir ],
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
 
+if ($supports_syncfs)
+{
+	command_ok([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+else
+{
+	command_fails([ 'initdb', '-S', $datadir, '--sync-method', 'syncfs' ],
+		'sync method syncfs');
+}
+
 # Check group access on PGDATA
 SKIP:
 {
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1a6eacf6d5..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -425,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2282,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2453,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 123450f483..9fb2c5b3c7 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -78,6 +78,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -436,6 +437,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -494,6 +496,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39a468b131..dc4a28e81e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -658,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1069,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index bdfacf3263..bfd44a284e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -108,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -219,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7afa96716e..842f3b6cd3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
-- 
2.25.1


--2fHTh5uZTiUOsy+g--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH v4 1/1] allow syncfs in frontend utilities
@ 2023-07-28 22:56 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Nathan Bossart @ 2023-07-28 22:56 UTC (permalink / raw)

---
 doc/src/sgml/ref/initdb.sgml          | 27 ++++++++
 doc/src/sgml/ref/pg_basebackup.sgml   | 30 +++++++++
 doc/src/sgml/ref/pg_checksums.sgml    | 27 ++++++++
 doc/src/sgml/ref/pg_dump.sgml         | 27 ++++++++
 doc/src/sgml/ref/pg_rewind.sgml       | 27 ++++++++
 doc/src/sgml/ref/pgupgrade.sgml       | 29 +++++++++
 src/backend/storage/file/fd.c         |  4 +-
 src/backend/utils/misc/guc_tables.c   |  7 ++-
 src/bin/initdb/initdb.c               | 12 +++-
 src/bin/pg_basebackup/pg_basebackup.c | 12 +++-
 src/bin/pg_checksums/pg_checksums.c   |  9 ++-
 src/bin/pg_dump/pg_backup.h           |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 14 +++--
 src/bin/pg_dump/pg_backup_archiver.h  |  1 +
 src/bin/pg_dump/pg_backup_directory.c |  2 +-
 src/bin/pg_dump/pg_dump.c             | 10 ++-
 src/bin/pg_rewind/file_ops.c          |  2 +-
 src/bin/pg_rewind/pg_rewind.c         |  9 +++
 src/bin/pg_rewind/pg_rewind.h         |  2 +
 src/bin/pg_upgrade/option.c           | 13 ++++
 src/bin/pg_upgrade/pg_upgrade.c       |  6 +-
 src/bin/pg_upgrade/pg_upgrade.h       |  1 +
 src/common/file_utils.c               | 91 ++++++++++++++++++++++++++-
 src/fe_utils/option_utils.c           | 24 +++++++
 src/include/common/file_utils.h       | 15 ++++-
 src/include/fe_utils/option_utils.h   |  4 ++
 src/include/storage/fd.h              | 10 ++-
 src/tools/pgindent/typedefs.list      |  1 +
 28 files changed, 388 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 22f1011781..872fef5705 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -365,6 +365,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-option-sync-method">
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>initdb</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>initdb</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-option-sync-only">
       <term><option>-S</option></term>
       <term><option>--sync-only</option></term>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 79d3e657c3..af8eb43251 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -594,6 +594,36 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_basebackup</command> will recursively open and synchronize
+        all files in the backup directory.  When the plain format is used, the
+        search for files will follow symbolic links for the WAL directory and
+        each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        backup directory.  When the plain format is used,
+        <command>pg_basebackup</command> will also synchronize the file systems
+        that contain the WAL files and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_basebackup</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml
index a3d0b0f0a3..70fb65a474 100644
--- a/doc/src/sgml/ref/pg_checksums.sgml
+++ b/doc/src/sgml/ref/pg_checksums.sgml
@@ -139,6 +139,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_checksums</command> will recursively open and synchronize
+        all files in the data directory.  The search for files will follow
+        symbolic links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk. Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_checksums</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-v</option></term>
       <term><option>--verbose</option></term>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a3cf0608f5..88139a3064 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1179,6 +1179,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_dump --format=directory</command> will recursively open and
+        synchronize all files in the archive directory.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file system that contains the
+        archive directory.  This may be a lot faster than the
+        <literal>fsync</literal> setting, because it doesn't need to open each
+        file one by one.  On the other hand, it may be slower if the file
+        system is shared by other applications that modify a lot of files,
+        since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_dump</command>, and relevant
+        error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used or
+        <option>--format</option> is not set to <literal>directory</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--table-and-children=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 15cddd086b..ed170a4c4c 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -284,6 +284,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_rewind</command> will recursively open and synchronize all
+        files in the data directory.  The search for files will follow symbolic
+        links for the WAL directory and each configured tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        data directory, the WAL files, and each tablespace.  This may be a lot
+        faster than the <literal>fsync</literal> setting, because it doesn't
+        need to open each file one by one.  On the other hand, it may be slower
+        if a file system is shared by other applications that modify a lot of
+        files, since those files will also be written to disk.  Furthermore, on
+        versions of Linux before 5.8, I/O errors encountered while writing data
+        to disk may not be reported to <command>pg_rewind</command>, and
+        relevant error messages may appear only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-V</option></term>
       <term><option>--version</option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 7816b4c685..dd2ae6cbc9 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -190,6 +190,35 @@ PostgreSQL documentation
       variable <envar>PGSOCKETDIR</envar></para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--sync-method</option></term>
+      <listitem>
+       <para>
+        When set to <literal>fsync</literal>, which is the default,
+        <command>pg_upgrade</command> will recursively open and synchronize all
+        files in the upgraded cluster's data directory.  The search for files
+        will follow symbolic links for the WAL directory and each configured
+        tablespace.
+       </para>
+       <para>
+        On Linux, <literal>syncfs</literal> may be used instead to ask the
+        operating system to synchronize the whole file systems that contain the
+        upgrade cluster's data directory, its WAL files, and each tablespace.
+        This may be a lot faster than the <literal>fsync</literal> setting,
+        because it doesn't need to open each file one by one.  On the other
+        hand, it may be slower if a file system is shared by other applications
+        that modify a lot of files, since those files will also be written to
+        disk.  Furthermore, on versions of Linux before 5.8, I/O errors
+        encountered while writing data to disk may not be reported to
+        <command>pg_upgrade</command>, and relevant error messages may appear
+        only in kernel logs.
+       </para>
+       <para>
+        This option has no effect when <option>--no-sync</option> is used.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-U</option> <replaceable>username</replaceable></term>
       <term><option>--username=</option><replaceable>username</replaceable></term>
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index a027a8aabc..206db91217 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -162,7 +162,7 @@ int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 bool		data_sync_retry = false;
 
 /* How SyncDataDirectory() should do its job. */
-int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;
+int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Which kinds of files should be opened with PG_O_DIRECT. */
 int			io_direct_flags;
@@ -3513,7 +3513,7 @@ SyncDataDirectory(void)
 	}
 
 #ifdef HAVE_SYNCFS
-	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
+	if (recovery_init_sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
 	{
 		DIR		   *dir;
 		struct dirent *de;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f9dba43b8c..331f65cbe6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,6 +41,7 @@
 #include "commands/trigger.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
+#include "common/file_utils.h"
 #include "common/scram-common.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -430,9 +431,9 @@ StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2)
 				 "array length mismatch");
 
 static const struct config_enum_entry recovery_init_sync_method_options[] = {
-	{"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false},
+	{"fsync", DATA_DIR_SYNC_METHOD_FSYNC, false},
 #ifdef HAVE_SYNCFS
-	{"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false},
+	{"syncfs", DATA_DIR_SYNC_METHOD_SYNCFS, false},
 #endif
 	{NULL, 0, false}
 };
@@ -4964,7 +4965,7 @@ struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Sets the method for synchronizing the data directory before crash recovery."),
 		},
 		&recovery_init_sync_method,
-		RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
+		DATA_DIR_SYNC_METHOD_FSYNC, recovery_init_sync_method_options,
 		NULL, NULL, NULL
 	},
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 3f4167682a..6d7d10cffb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -76,6 +76,7 @@
 #include "common/restricted_token.h"
 #include "common/string.h"
 #include "common/username.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
@@ -165,6 +166,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 
 /* internal vars */
@@ -2466,6 +2468,7 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
 	printf(_("      --no-instructions     do not print instructions for next steps\n"));
 	printf(_("  -s, --show                show internal settings\n"));
+	printf(_("      --sync-method=METHOD  set method for syncing files to disk\n"));
 	printf(_("  -S, --sync-only           only sync database files to disk, then exit\n"));
 	printf(_("\nOther options:\n"));
 	printf(_("  -V, --version             output version information, then exit\n"));
@@ -3106,6 +3109,7 @@ main(int argc, char *argv[])
 		{"locale-provider", required_argument, NULL, 15},
 		{"icu-locale", required_argument, NULL, 16},
 		{"icu-rules", required_argument, NULL, 17},
+		{"sync-method", required_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3285,6 +3289,10 @@ main(int argc, char *argv[])
 			case 17:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 18:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -3332,7 +3340,7 @@ main(int argc, char *argv[])
 
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 		return 0;
 	}
@@ -3409,7 +3417,7 @@ main(int argc, char *argv[])
 	{
 		fputs(_("syncing data to disk ... "), stdout);
 		fflush(stdout);
-		fsync_pgdata(pg_data, PG_VERSION_NUM);
+		fsync_pgdata(pg_data, PG_VERSION_NUM, sync_method);
 		check_ok();
 	}
 	else
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1dc8efe0cb..977c793a67 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -148,6 +148,7 @@ static bool verify_checksums = true;
 static bool manifest = true;
 static bool manifest_force_encode = false;
 static char *manifest_checksums = NULL;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 static bool success = false;
 static bool made_new_pgdata = false;
@@ -424,6 +425,8 @@ usage(void)
 	printf(_("      --no-slot          prevent creation of temporary replication slot\n"));
 	printf(_("      --no-verify-checksums\n"
 			 "                         do not verify checksums\n"));
+	printf(_("      --sync-method=METHOD\n"
+			 "                         set method for syncing files to disk\n"));
 	printf(_("  -?, --help             show this help, then exit\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
@@ -2199,11 +2202,11 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 		if (format == 't')
 		{
 			if (strcmp(basedir, "-") != 0)
-				(void) fsync_dir_recurse(basedir);
+				(void) fsync_dir_recurse(basedir, sync_method);
 		}
 		else
 		{
-			(void) fsync_pgdata(basedir, serverVersion);
+			(void) fsync_pgdata(basedir, serverVersion, sync_method);
 		}
 	}
 
@@ -2281,6 +2284,7 @@ main(int argc, char **argv)
 		{"no-manifest", no_argument, NULL, 5},
 		{"manifest-force-encode", no_argument, NULL, 6},
 		{"manifest-checksums", required_argument, NULL, 7},
+		{"sync-method", required_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
@@ -2452,6 +2456,10 @@ main(int argc, char **argv)
 			case 7:
 				manifest_checksums = pg_strdup(optarg);
 				break;
+			case 8:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 19eb67e485..adcc767b0f 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -44,6 +44,7 @@ static char *only_filenode = NULL;
 static bool do_sync = true;
 static bool verbose = false;
 static bool showprogress = false;
+static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 typedef enum
 {
@@ -87,6 +88,7 @@ usage(void)
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
+	printf(_("      --sync-method=METHOD set method for syncing files to disk\n"));
 	printf(_("  -v, --verbose            output verbose messages\n"));
 	printf(_("  -V, --version            output version information, then exit\n"));
 	printf(_("  -?, --help               show this help, then exit\n"));
@@ -445,6 +447,7 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
+		{"sync-method", required_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -503,6 +506,10 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 1:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -633,7 +640,7 @@ main(int argc, char *argv[])
 		if (do_sync)
 		{
 			pg_log_info("syncing data directory");
-			fsync_pgdata(DataDir, PG_VERSION_NUM);
+			fsync_pgdata(DataDir, PG_VERSION_NUM, sync_method);
 		}
 
 		pg_log_info("updating control file");
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..3a57cdd97d 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -24,6 +24,7 @@
 #define PG_BACKUP_H
 
 #include "common/compression.h"
+#include "common/file_utils.h"
 #include "fe_utils/simple_list.h"
 #include "libpq-fe.h"
 
@@ -307,7 +308,8 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 							  const pg_compress_specification compression_spec,
 							  bool dosync, ArchiveMode mode,
-							  SetupWorkerPtrType setupDumpWorker);
+							  SetupWorkerPtrType setupDumpWorker,
+							  DataDirSyncMethod sync_method);
 
 /* The --list option */
 extern void PrintTOCSummary(Archive *AHX);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 39ebcfec32..4d83381d84 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -66,7 +66,8 @@ typedef struct _parallelReadyList
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
 							   const pg_compress_specification compression_spec,
 							   bool dosync, ArchiveMode mode,
-							   SetupWorkerPtrType setupWorkerPtr);
+							   SetupWorkerPtrType setupWorkerPtr,
+							   DataDirSyncMethod sync_method);
 static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
 static char *sanitize_line(const char *str, bool want_hyphen);
@@ -238,11 +239,12 @@ Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 			  const pg_compress_specification compression_spec,
 			  bool dosync, ArchiveMode mode,
-			  SetupWorkerPtrType setupDumpWorker)
+			  SetupWorkerPtrType setupDumpWorker,
+			  DataDirSyncMethod sync_method)
 
 {
 	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression_spec,
-								 dosync, mode, setupDumpWorker);
+								 dosync, mode, setupDumpWorker, sync_method);
 
 	return (Archive *) AH;
 }
@@ -257,7 +259,8 @@ OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 
 	compression_spec.algorithm = PG_COMPRESSION_NONE;
 	AH = _allocAH(FileSpec, fmt, compression_spec, true,
-				  archModeRead, setupRestoreWorker);
+				  archModeRead, setupRestoreWorker,
+				  DATA_DIR_SYNC_METHOD_FSYNC);
 
 	return (Archive *) AH;
 }
@@ -2233,7 +2236,7 @@ static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 		 const pg_compress_specification compression_spec,
 		 bool dosync, ArchiveMode mode,
-		 SetupWorkerPtrType setupWorkerPtr)
+		 SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method)
 {
 	ArchiveHandle *AH;
 	CompressFileHandle *CFH;
@@ -2287,6 +2290,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->mode = mode;
 	AH->compression_spec = compression_spec;
 	AH->dosync = dosync;
+	AH->sync_method = sync_method;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 18b38c17ab..b07673933d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -312,6 +312,7 @@ struct _archiveHandle
 	pg_compress_specification compression_spec; /* Requested specification for
 												 * compression */
 	bool		dosync;			/* data requested to be synced on sight */
+	DataDirSyncMethod sync_method;
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
 
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f2ac7c7fd..6faa3a511f 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -613,7 +613,7 @@ _CloseArchive(ArchiveHandle *AH)
 		 * individually. Just recurse once through all the files generated.
 		 */
 		if (AH->dosync)
-			fsync_dir_recurse(ctx->directory);
+			fsync_dir_recurse(ctx->directory, AH->sync_method);
 	}
 	AH->FH = NULL;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..895360dac7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -357,6 +357,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm_str = "none";
 	char	   *error_detail = NULL;
 	bool		user_compression_defined = false;
+	DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 	static DumpOptions dopt;
 
@@ -431,6 +432,7 @@ main(int argc, char **argv)
 		{"table-and-children", required_argument, NULL, 12},
 		{"exclude-table-and-children", required_argument, NULL, 13},
 		{"exclude-table-data-and-children", required_argument, NULL, 14},
+		{"sync-method", required_argument, NULL, 15},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -657,6 +659,11 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 15:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit_nicely(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -777,7 +784,7 @@ main(int argc, char **argv)
 
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compression_spec,
-						 dosync, archiveMode, setupDumpWorker);
+						 dosync, archiveMode, setupDumpWorker, sync_method);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -1068,6 +1075,7 @@ help(const char *progname)
 			 "                               compress as specified\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --sync-method=METHOD         set method for syncing files to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
 
 	printf(_("\nOptions controlling the output content:\n"));
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 25996b4da4..451fb1856e 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -296,7 +296,7 @@ sync_target_dir(void)
 	if (!do_sync || dry_run)
 		return;
 
-	fsync_pgdata(datadir_target, PG_VERSION_NUM);
+	fsync_pgdata(datadir_target, PG_VERSION_NUM, sync_method);
 }
 
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index f7f3b8227f..64a7469f22 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "file_ops.h"
@@ -74,6 +75,7 @@ bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
 bool		restore_wal = false;
+DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -107,6 +109,7 @@ usage(const char *progname)
 			 "                                 file when running target cluster\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
+	printf(_("      --sync-method=METHOD       set method for syncing files to disk\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
@@ -131,6 +134,7 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+		{"sync-method", required_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -218,6 +222,11 @@ main(int argc, char **argv)
 				config_file = pg_strdup(optarg);
 				break;
 
+			case 6:
+				if (!parse_sync_method(optarg, &sync_method))
+					exit(1);
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index ef8bdc1fbb..05729adfef 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -13,6 +13,7 @@
 
 #include "access/timeline.h"
 #include "common/logging.h"
+#include "common/file_utils.h"
 #include "datapagemap.h"
 #include "libpq-fe.h"
 #include "storage/block.h"
@@ -24,6 +25,7 @@ extern bool showprogress;
 extern bool dry_run;
 extern bool do_sync;
 extern int	WalSegSz;
+extern DataDirSyncMethod sync_method;
 
 /* Target history */
 extern TimeLineHistoryEntry *targetHistory;
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 640361009e..b9d900d0db 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -14,6 +14,7 @@
 #endif
 
 #include "common/string.h"
+#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
@@ -57,12 +58,14 @@ parseCommandLine(int argc, char *argv[])
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
 		{"copy", no_argument, NULL, 2},
+		{"sync-method", required_argument, NULL, 3},
 
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
 	int			optindex = 0;	/* used by getopt_long */
 	int			os_user_effective_id;
+	DataDirSyncMethod unused;
 
 	user_opts.do_sync = true;
 	user_opts.transfer_mode = TRANSFER_MODE_COPY;
@@ -199,6 +202,12 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_COPY;
 				break;
 
+			case 3:
+				if (!parse_sync_method(optarg, &unused))
+					exit(1);
+				user_opts.sync_method = pg_strdup(optarg);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -209,6 +218,9 @@ parseCommandLine(int argc, char *argv[])
 	if (optind < argc)
 		pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
 
+	if (!user_opts.sync_method)
+		user_opts.sync_method = pg_strdup("fsync");
+
 	if (log_opts.verbose)
 		pg_log(PG_REPORT, "Running in verbose mode");
 
@@ -289,6 +301,7 @@ usage(void)
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
 	printf(_("  --copy                        copy files to new cluster (default)\n"));
+	printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4562dafcff..96bfb67167 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -192,8 +192,10 @@ main(int argc, char **argv)
 	{
 		prep_status("Sync data directory to disk");
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
-				  new_cluster.pgdata);
+				  "\"%s/initdb\" --sync-only \"%s\" --sync-method %s",
+				  new_cluster.bindir,
+				  new_cluster.pgdata,
+				  user_opts.sync_method);
 		check_ok();
 	}
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 3eea0139c7..13457b2f75 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -304,6 +304,7 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	char	   *sync_method;
 } UserOpts;
 
 typedef struct
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 74833c4acb..c977c990ad 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -51,6 +51,31 @@ static void walkdir(const char *path,
 					int (*action) (const char *fname, bool isdir),
 					bool process_symlinks);
 
+#ifdef HAVE_SYNCFS
+static void
+do_syncfs(const char *path)
+{
+	int			fd;
+
+	fd = open(path, O_RDONLY, 0);
+
+	if (fd < 0)
+	{
+		pg_log_error("could not open file \"%s\": %m", path);
+		return;
+	}
+
+	if (syncfs(fd) < 0)
+	{
+		pg_log_error("could not synchronize file system for file \"%s\": %m", path);
+		(void) close(fd);
+		exit(EXIT_FAILURE);
+	}
+
+	(void) close(fd);
+}
+#endif
+
 /*
  * Issue fsync recursively on PGDATA and all its contents.
  *
@@ -63,7 +88,8 @@ static void walkdir(const char *path,
  */
 void
 fsync_pgdata(const char *pg_data,
-			 int serverVersion)
+			 int serverVersion,
+			 DataDirSyncMethod sync_method)
 {
 	bool		xlog_is_symlink;
 	char		pg_wal[MAXPGPATH];
@@ -89,6 +115,55 @@ fsync_pgdata(const char *pg_data,
 			xlog_is_symlink = true;
 	}
 
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		DIR		   *dir;
+		struct dirent *de;
+
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync whole filesystems.  We only expect
+		 * filesystem boundaries to exist where we tolerate symlinks, namely
+		 * pg_wal and the tablespaces, so we call syncfs() for each of those
+		 * directories.
+		 */
+
+		/* Sync the top level pgdata directory. */
+		do_syncfs(pg_data);
+
+		/* If any tablespaces are configured, sync each of those. */
+		dir = opendir(pg_tblspc);
+		if (dir == NULL)
+			pg_log_error("could not open directory \"%s\": %m", pg_tblspc);
+		else
+		{
+			while (errno = 0, (de = readdir(dir)) != NULL)
+			{
+				char		subpath[MAXPGPATH * 2];
+
+				if (strcmp(de->d_name, ".") == 0 ||
+					strcmp(de->d_name, "..") == 0)
+					continue;
+
+				snprintf(subpath, sizeof(subpath), "%s/%s", pg_tblspc, de->d_name);
+				do_syncfs(subpath);
+			}
+
+			if (errno)
+				pg_log_error("could not read directory \"%s\": %m", pg_tblspc);
+
+			(void) closedir(dir);
+		}
+
+		/* If pg_wal is a symlink, process that too. */
+		if (xlog_is_symlink)
+			do_syncfs(pg_wal);
+
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
@@ -121,8 +196,20 @@ fsync_pgdata(const char *pg_data,
  * This is a convenient wrapper on top of walkdir().
  */
 void
-fsync_dir_recurse(const char *dir)
+fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method)
 {
+#ifdef HAVE_SYNCFS
+	if (sync_method == DATA_DIR_SYNC_METHOD_SYNCFS)
+	{
+		/*
+		 * On Linux, we don't have to open every single file one by one.  We
+		 * can use syncfs() to sync the whole filesystem.
+		 */
+		do_syncfs(dir);
+		return;
+	}
+#endif							/* HAVE_SYNCFS */
+
 	/*
 	 * If possible, hint to the kernel that we're soon going to fsync the data
 	 * directory and its contents.
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 763c991015..36ac689964 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -82,3 +82,27 @@ option_parse_int(const char *optarg, const char *optname,
 		*result = val;
 	return true;
 }
+
+bool
+parse_sync_method(const char *optarg, DataDirSyncMethod *sync_method)
+{
+	if (strcmp(optarg, "fsync") == 0)
+		*sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+	else if (strcmp(optarg, "syncfs") == 0)
+	{
+#ifdef HAVE_SYNCFS
+		*sync_method = DATA_DIR_SYNC_METHOD_SYNCFS;
+#else
+		pg_log_error("this build does not support sync method \"%s\"",
+					 "syncfs");
+		return false;
+#endif
+	}
+	else
+	{
+		pg_log_error("unrecognized sync method: %s", optarg);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index b7efa1226d..93aa5bdaf8 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -26,13 +26,22 @@ typedef enum PGFileType
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+typedef enum DataDirSyncMethod
+{
+	DATA_DIR_SYNC_METHOD_FSYNC,
+	DATA_DIR_SYNC_METHOD_SYNCFS
+} DataDirSyncMethod;
+
 #ifdef FRONTEND
+
 extern int	fsync_fname(const char *fname, bool isdir);
-extern void fsync_pgdata(const char *pg_data, int serverVersion);
-extern void fsync_dir_recurse(const char *dir);
+extern void fsync_pgdata(const char *pg_data, int serverVersion,
+						 DataDirSyncMethod sync_method);
+extern void fsync_dir_recurse(const char *dir, DataDirSyncMethod sync_method);
 extern int	durable_rename(const char *oldfile, const char *newfile);
 extern int	fsync_parent_path(const char *fname);
-#endif
+
+#endif							/* FRONTEND */
 
 extern PGFileType get_dirent_type(const char *path,
 								  const struct dirent *de,
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index b7b0654cee..6f3a965916 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,6 +14,8 @@
 
 #include "postgres_fe.h"
 
+#include "common/file_utils.h"
+
 typedef void (*help_handler) (const char *progname);
 
 extern void handle_help_version_opts(int argc, char *argv[],
@@ -22,5 +24,7 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
+extern bool parse_sync_method(const char *optarg,
+							  DataDirSyncMethod *sync_method);
 
 #endif							/* OPTION_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 6791a406fc..dedb773304 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -43,15 +43,11 @@
 #ifndef FD_H
 #define FD_H
 
+#ifndef FRONTEND
+
 #include <dirent.h>
 #include <fcntl.h>
 
-typedef enum RecoveryInitSyncMethod
-{
-	RECOVERY_INIT_SYNC_METHOD_FSYNC,
-	RECOVERY_INIT_SYNC_METHOD_SYNCFS
-}			RecoveryInitSyncMethod;
-
 typedef int File;
 
 
@@ -195,6 +191,8 @@ extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
+#endif							/* ! FRONTEND */
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 51b7951ad8..07387ed251 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2678,6 +2678,7 @@ SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
 Syn
+DataDirSyncMethod
 SyncOps
 SyncRepConfigData
 SyncRepStandbyData
-- 
2.25.1


--k+w/mQv8wyuph6w0--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Implement waiting for wal lsn replay: reloaded
@ 2024-11-27 04:08 Alexander Korotkov <[email protected]>
  2024-12-04 11:12 ` Re: Implement waiting for wal lsn replay: reloaded Kirill Reshke <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Alexander Korotkov @ 2024-11-27 04:08 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

Hi!

Introduction

The simple way to wait for a given lsn to replay on standby appears to
be useful because it provides a way to achieve read-your-writes
consistency while working with both replication leader and standby.
And it's both handy and cheaper to have built-in functionality for
that instead of polling pg_last_wal_replay_lsn().

Key problem

While this feature generally looks trivial, there is a surprisingly
hard problem.  While waiting for an LSN to replay, you should hold any
snapshots.  If you hold a snapshot on standby, that snapshot could
prevent the replay of WAL records.  In turn, that could prevent the
wait to finish, causing a kind of deadlock.  Therefore, waiting for
LSN to replay couldn't be implemented as a function.  My last attempt
implements this functionality as a stored procedure [1].  This
approach generally works but has a couple of serious limitations.
1) Given that a CALL statement has to lookup a catalog for the stored
procedure, we can't work inside a transaction of REPEATABLE READ or a
higher isolation level (even if nothing has been done before in that
transaction).  It is especially unpleasant that this limitation covers
the case of the implicit transaction when
default_transaction_isolation = 'repeatable read' [2].  I had a
workaround for that [3], but it looks a bit awkward.
2) Using output parameters for a stored procedure causes an extra
snapshot to be held.  And that snapshot is difficult (unsafe?) to
release [3].

Present solution

The present patch implements a new utility command WAIT FOR LSN
'target_lsn' [, TIMEOUT 'timeout'][, THROW 'throw'].  Unlike previous
attempts to implement custom syntax, it uses only one extra unreserved
keyword.  The parameters are implemented as generic_option_list.

Custom syntax eliminates the problem of running within an empty
transaction of REPEATABLE READ level or higher.  We don't need to
lookup a system catalog.  Thus, we have to set a transaction snapshot.

Also, revising PlannedStmtRequiresSnapshot() allows us to avoid
holding a snapshot to return a value.  Therefore, the WAIT command in
the attached patch returns its result status.

Also, the attached patch explicitly checks if the standby has been
promoted to throw the most relevant form of an error.  The issue of
inaccurate error messages has been previously spotted in [5].

Any comments?

Links.
1. https://www.postgresql.org/message-id/E1sZwuz-002NPQ-Lc%40gemulon.postgresql.org
2. https://www.postgresql.org/message-id/14de8671-e328-4c3e-b136-664f6f13a39f%40iki.fi
3. https://www.postgresql.org/message-id/CAPpHfdvRmTzGJw5rQdSMkTxUPZkjwtbQ%3DLJE2u9Jqh9gFXHpmg%40mail.g...
4. https://www.postgresql.org/message-id/4953563546cb8c8851f84c7debf723ef%40postgrespro.ru
5. https://www.postgresql.org/message-id/ab0eddce-06d4-4db2-87ce-46fa2427806c%40iki.fi

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v1-0001-Implement-WAIT-FOR-command.patch (45.4K, ../../CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com/2-v1-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 496808d1e9af1ae20bab59761be9d27c0cbaca2a Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Tue, 19 Nov 2024 07:16:41 +0200
Subject: [PATCH v1] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan, Alexander Korotkov
Reviewed-by: Michael Paquier, Peter Eisentraut, Dilip Kumar, Amit Kapila
Reviewed-by: Alexander Lakhin, Bharath Rupireddy, Euler Taveira
Reviewed-by: Heikki Linnakangas, Kyotaro Horiguchi
---
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 336 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 185 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  14 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/043_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 28 files changed, 966 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/043_wait_for_lsn.pl

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..8b585cba751 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY wait               SYSTEM "wait.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..bd14ec00d2d 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &wait;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 8a3522557cd..91d258f9df1 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b7ebcc2a557..004f7e10e55 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6f58412bcab..f14d3933aec 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6173,6 +6174,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 05c738d6614..869cb524082 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1828,6 +1829,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..313c8cc35df
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,336 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber *wakeUpProcs;
+	int			numWakeUpProcs = 0;
+
+	wakeUpProcs = palloc(sizeof(ProcNumber) * MaxBackends);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+	{
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+	}
+	pfree(wakeUpProcs);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91c..d8f6965d8c6 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -61,6 +61,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 6dd00a4abde..3f06dc53410 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -50,4 +50,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..3cc5b2e832f
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	char	   *result;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (GetOldestSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index fe1deba13ec..7858e5e076b 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396af..7b692954f20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -299,7 +299,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -778,7 +778,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1106,6 +1106,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16266,6 +16267,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17922,6 +17931,7 @@ unreserved_keyword:
 			| VIEW
 			| VIEWS
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 7783ba854fc..d68aa29d93e 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 720ef99ee83..1f4c93520ff 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -891,6 +892,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 0c45fcf318f..116642b81b6 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1168,10 +1168,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1760,7 +1761,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f28bf371059..1507f784ac0 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 16144c2b72d..8efb4044d6f 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -87,6 +87,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -345,6 +346,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..41234f6b961
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..a7fa00ed41e
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 7eade81535a..9e1c26033a1 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0f9462493e3..1502be41688 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4258,4 +4258,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 899d64ad55f..87c58d2063b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -491,6 +491,7 @@ PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 6a2f64c54fb..88dc79b2bd6 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 7fdcec6dd93..02a6d576f08 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index b1eb77b1ec1..32040d43550 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -51,6 +51,7 @@ tests += {
       't/040_standby_failover_slots_sync.pl',
       't/041_checkpoint_at_promote.pl',
       't/042_low_level_backup.pl',
+      't/043_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/043_wait_for_lsn.pl b/src/test/recovery/t/043_wait_for_lsn.pl
new file mode 100644
index 00000000000..79c2c49b9ce
--- /dev/null
+++ b/src/test/recovery/t/043_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b54428b38cd..cac2424a99b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3129,7 +3129,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2024-12-04 11:12 ` Kirill Reshke <[email protected]>
  2025-02-06 07:42   ` Re: Implement waiting for wal lsn replay: reloaded Andrei Lepikhov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Kirill Reshke @ 2024-12-04 11:12 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Wed, 27 Nov 2024 at 09:09, Alexander Korotkov <[email protected]> wrote:
>
> Hi!
>
> Introduction
>
> The simple way to wait for a given lsn to replay on standby appears to
> be useful because it provides a way to achieve read-your-writes
> consistency while working with both replication leader and standby.
> And it's both handy and cheaper to have built-in functionality for
> that instead of polling pg_last_wal_replay_lsn().
>
> Key problem
>
> While this feature generally looks trivial, there is a surprisingly
> hard problem.  While waiting for an LSN to replay, you should hold any
> snapshots.  If you hold a snapshot on standby, that snapshot could
> prevent the replay of WAL records.  In turn, that could prevent the
> wait to finish, causing a kind of deadlock.  Therefore, waiting for
> LSN to replay couldn't be implemented as a function.  My last attempt
> implements this functionality as a stored procedure [1].  This
> approach generally works but has a couple of serious limitations.
> 1) Given that a CALL statement has to lookup a catalog for the stored
> procedure, we can't work inside a transaction of REPEATABLE READ or a
> higher isolation level (even if nothing has been done before in that
> transaction).  It is especially unpleasant that this limitation covers
> the case of the implicit transaction when
> default_transaction_isolation = 'repeatable read' [2].  I had a
> workaround for that [3], but it looks a bit awkward.
> 2) Using output parameters for a stored procedure causes an extra
> snapshot to be held.  And that snapshot is difficult (unsafe?) to
> release [3].
>
> Present solution
>
> The present patch implements a new utility command WAIT FOR LSN
> 'target_lsn' [, TIMEOUT 'timeout'][, THROW 'throw'].  Unlike previous
> attempts to implement custom syntax, it uses only one extra unreserved
> keyword.  The parameters are implemented as generic_option_list.
>
> Custom syntax eliminates the problem of running within an empty
> transaction of REPEATABLE READ level or higher.  We don't need to
> lookup a system catalog.  Thus, we have to set a transaction snapshot.
>
> Also, revising PlannedStmtRequiresSnapshot() allows us to avoid
> holding a snapshot to return a value.  Therefore, the WAIT command in
> the attached patch returns its result status.
>
> Also, the attached patch explicitly checks if the standby has been
> promoted to throw the most relevant form of an error.  The issue of
> inaccurate error messages has been previously spotted in [5].
>
> Any comments?
>
> Links.
> 1. https://www.postgresql.org/message-id/E1sZwuz-002NPQ-Lc%40gemulon.postgresql.org
> 2. https://www.postgresql.org/message-id/14de8671-e328-4c3e-b136-664f6f13a39f%40iki.fi
> 3. https://www.postgresql.org/message-id/CAPpHfdvRmTzGJw5rQdSMkTxUPZkjwtbQ%3DLJE2u9Jqh9gFXHpmg%40mail.g...
> 4. https://www.postgresql.org/message-id/4953563546cb8c8851f84c7debf723ef%40postgrespro.ru
> 5. https://www.postgresql.org/message-id/ab0eddce-06d4-4db2-87ce-46fa2427806c%40iki.fi
>
> ------
> Regards,
> Alexander Korotkov
> Supabase
Hi!

What's the current status of
https://commitfest.postgresql.org/50/5167/ ? Should we close it or
reattach to this thread?


-- 
Best regards,
Kirill Reshke





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2024-12-04 11:12 ` Re: Implement waiting for wal lsn replay: reloaded Kirill Reshke <[email protected]>
@ 2025-02-06 07:42   ` Andrei Lepikhov <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Andrei Lepikhov @ 2025-02-06 07:42 UTC (permalink / raw)
  To: Kirill Reshke <[email protected]>; Alexander Korotkov <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On 12/4/24 18:12, Kirill Reshke wrote:
> On Wed, 27 Nov 2024 at 09:09, Alexander Korotkov <[email protected]> wrote:
>> Any comments?
> What's the current status of
> https://commitfest.postgresql.org/50/5167/ ? Should we close it or
> reattach to this thread?
To push this feature further I rebased the patch onto current master.
Also, let's add a commitfest entry:
https://commitfest.postgresql.org/52/5550/

-- 
regards, Andrei Lepikhov

Attachments:

  [text/x-patch] v2-0001-Implement-WAIT-FOR-command.patch (45.5K, ../../[email protected]/2-v2-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From ea224b84d343ea726f47af30a7a974e0736d79cc Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <[email protected]>
Date: Thu, 6 Feb 2025 14:13:09 +0700
Subject: [PATCH v2] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan, Alexander Korotkov
Reviewed-by: Michael Paquier, Peter Eisentraut, Dilip Kumar, Amit Kapila
Reviewed-by: Alexander Lakhin, Bharath Rupireddy, Euler Taveira
Reviewed-by: Heikki Linnakangas, Kyotaro Horiguchi
---
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 336 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 185 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  15 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/044_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 28 files changed, 967 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/044_wait_for_lsn.pl

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867..8b585cba75 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY wait               SYSTEM "wait.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83f..bd14ec00d2 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &wait;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..a32f473e0a 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8..74a62ab3ea 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d331ab90d7..8336bb0cd1 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 9c270e7d46..62c37f31ee 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6194,6 +6195,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 473de6710d..5364576ca5 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1829,6 +1830,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 0000000000..313c8cc35d
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,336 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber *wakeUpProcs;
+	int			numWakeUpProcs = 0;
+
+	wakeUpProcs = palloc(sizeof(ProcNumber) * MaxBackends);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+	{
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+	}
+	pfree(wakeUpProcs);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..d8f6965d8c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -61,6 +61,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ef0d407a38..f5db28bbd2 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -50,4 +50,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 0000000000..8351733500
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	char	   *result;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1..fa8431f794 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d7f9c00c40..67aa9554e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1114,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16334,6 +16335,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17991,6 +18000,7 @@ unreserved_keyword:
 			| VIEW
 			| VIEWS
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18646,6 +18656,7 @@ bare_label_keyword:
 			| VIEW
 			| VIEWS
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 174eed7036..27b447b7a7 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 49204f91a2..dbb613663f 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -896,6 +897,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 6f22496305..661296107c 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1162,10 +1162,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1751,7 +1752,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d5801..d23ac3b0f0 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index e199f07162..3b282043ec 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -88,6 +88,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -346,6 +347,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 0000000000..41234f6b96
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 0000000000..a7fa00ed41
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1..567586f2ec 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ffe155ee20..3dc1c1a56f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4305,4 +4305,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index cf2917ad07..0d0d8f4ab4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -492,6 +492,7 @@ PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index cf56545238..a3f6607128 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d5..c4606d6504 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 0428704dbf..c1328b1e16 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -52,6 +52,7 @@ tests += {
       't/041_checkpoint_at_promote.pl',
       't/042_low_level_backup.pl',
       't/043_no_contrecord_switch.pl',
+	  't/044_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/044_wait_for_lsn.pl b/src/test/recovery/t/044_wait_for_lsn.pl
new file mode 100644
index 0000000000..79c2c49b9c
--- /dev/null
+++ b/src/test/recovery/t/044_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a3bee93de..1e0be9f4f6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3149,7 +3149,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-02-06 08:31 ` Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Yura Sokolov @ 2025-02-06 08:31 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; PostgreSQL Hackers <[email protected]>

27.11.2024 07:08, Alexander Korotkov wrote:
> Present solution
> 
> The present patch implements a new utility command WAIT FOR LSN
> 'target_lsn' [, TIMEOUT 'timeout'][, THROW 'throw'].  Unlike previous
> attempts to implement custom syntax, it uses only one extra unreserved
> keyword.  The parameters are implemented as generic_option_list.
> 
> Custom syntax eliminates the problem of running within an empty
> transaction of REPEATABLE READ level or higher.  We don't need to
> lookup a system catalog.  Thus, we have to set a transaction snapshot.
> 
> Also, revising PlannedStmtRequiresSnapshot() allows us to avoid
> holding a snapshot to return a value.  Therefore, the WAIT command in
> the attached patch returns its result status.
> 
> Also, the attached patch explicitly checks if the standby has been
> promoted to throw the most relevant form of an error.  The issue of
> inaccurate error messages has been previously spotted in [5].
> 
> Any comments?

Good day, Alexander.

I briefly looked into patch and have couple of minor remarks:

1. I don't like `palloc` in the `WaitLSNWakeup`. I believe it wont issue
problems, but still don't like it. I'd prefer to see local fixed array, say
of 16 elements, and loop around remaining function body acting in batch of
16 wakeups. Doubtfully there will be more than 16 waiting clients often,
and even then it wont be much heavier than fetching all at once.

2. I'd move `inHeap` field between `procno` and `phNode` to fill the gap
between fields on 64bit platforms.
Well, I believe, it would be better to tweak `pairingheap_node` to make it
clear if it is in heap or not. But such change would be unrelated to
current patch's sense. So lets stick with `inHeap`, but move it a bit.

Non-code question: do you imagine for `WAIT` command reuse for other cases?
Is syntax rule in gram.y convenient enough for such reuse? I believe, `LSN`
is not part of syntax to not introduce new keyword. But is it correct way?
I have no answer or strong opinion.

Otherwise, the patch looks quite strong to me.

-------
regards
Yura Sokolov





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
@ 2025-02-16 21:27   ` Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-02-16 21:27 UTC (permalink / raw)
  To: Yura Sokolov <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Hi, Yura!


On Thu, Feb 6, 2025 at 10:31 AM Yura Sokolov <[email protected]> wrote:
> I briefly looked into patch and have couple of minor remarks:
>
> 1. I don't like `palloc` in the `WaitLSNWakeup`. I believe it wont issue
> problems, but still don't like it. I'd prefer to see local fixed array, say
> of 16 elements, and loop around remaining function body acting in batch of
> 16 wakeups. Doubtfully there will be more than 16 waiting clients often,
> and even then it wont be much heavier than fetching all at once.

OK, I've refactored this to use static array of 16 size.  palloc() is
used only if we don't fit static array.

> 2. I'd move `inHeap` field between `procno` and `phNode` to fill the gap
> between fields on 64bit platforms.
> Well, I believe, it would be better to tweak `pairingheap_node` to make it
> clear if it is in heap or not. But such change would be unrelated to
> current patch's sense. So lets stick with `inHeap`, but move it a bit.

Ok, `inHeap` is moved.

> Non-code question: do you imagine for `WAIT` command reuse for other cases?
> Is syntax rule in gram.y convenient enough for such reuse? I believe, `LSN`
> is not part of syntax to not introduce new keyword. But is it correct way?
> I have no answer or strong opinion.

This is conscious decision.  New rules and new keywords causes extra
states for parser state machine.  There could be raised a question
whether feature is valuable enough to justify the slowdown of parser.
This is why I tried to make this feature as less invasive as possible
in terms of parser.  And yes, there potentially could be other things
to wait.  For instance, instead of waiting for lsn replay we could be
waiting for finishing replay of given xid.

> Otherwise, the patch looks quite strong to me.

Great, thank you!

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v2-0001-Implement-WAIT-FOR-command.patch (46.0K, ../../CAPpHfdsZiTc7eKaqjJhKTgDPMyz-0r2nNRQg+rp9zvSAxe7cow@mail.gmail.com/2-v2-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 6324f7496fac463d98857b2c8ac9cbe3f2f40abf Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Tue, 19 Nov 2024 07:16:41 +0200
Subject: [PATCH v2] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan, Alexander Korotkov
Reviewed-by: Michael Paquier, Peter Eisentraut, Dilip Kumar, Amit Kapila
Reviewed-by: Alexander Lakhin, Bharath Rupireddy, Euler Taveira
Reviewed-by: Heikki Linnakangas, Kyotaro Horiguchi
---
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 351 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 185 +++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  14 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/044_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 28 files changed, 981 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/044_wait_for_lsn.pl

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..8b585cba751 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY wait               SYSTEM "wait.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..bd14ec00d2d 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &wait;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1b4f21a88d3..e617ae8ead5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a50fd99d9e5..12ea4f2cb45 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6194,6 +6195,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 473de6710d7..5364576ca5a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1829,6 +1830,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..5b70ba90ec1
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,351 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to avoid palloc() for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcsStatic[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	ProcNumber *wakeUpProcs = wakeUpProcsStatic;
+	int			numWakeUpProcs = 0;
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		/*
+		 * Check if we don't fit to WAKEUP_PROC_STATIC_ARRAY_SIZE.  Otherwise,
+		 * allocate entries for every backend.  It should be enough for every
+		 * case.
+		 */
+		if (wakeUpProcs == wakeUpProcsStatic &&
+			numWakeUpProcs >= WAKEUP_PROC_STATIC_ARRAY_SIZE)
+			wakeUpProcs = palloc(sizeof(ProcNumber) * MaxBackends);
+
+		Assert(numWakeUpProcs < MaxBackends);
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	if (wakeUpProcs != wakeUpProcsStatic)
+		pfree(wakeUpProcs);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91c..d8f6965d8c6 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -61,6 +61,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ef0d407a383..f5db28bbd22 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -50,4 +50,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..83517335003
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	char	   *result;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d3887628d46..4f8f242b2cf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1114,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16341,6 +16342,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17999,6 +18008,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 174eed70367..27b447b7a7a 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 49204f91a20..dbb613663fa 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -896,6 +897,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 6f22496305a..661296107ce 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1162,10 +1162,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1751,7 +1752,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..d23ac3b0f0b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index e199f071628..3b282043eca 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -88,6 +88,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -346,6 +347,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..0acc61eba5f
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..a7fa00ed41e
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8dd421fa0ef..08fb233ecae 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4306,4 +4306,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..6d834f25d2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -493,6 +493,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index cf565452382..a3f66071288 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 0428704dbfd..52ec036e27e 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -52,6 +52,7 @@ tests += {
       't/041_checkpoint_at_promote.pl',
       't/042_low_level_backup.pl',
       't/043_no_contrecord_switch.pl',
+      't/044_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/044_wait_for_lsn.pl b/src/test/recovery/t/044_wait_for_lsn.pl
new file mode 100644
index 00000000000..79c2c49b9ce
--- /dev/null
+++ b/src/test/recovery/t/044_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b6c170ac249..6b05cd3842f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3151,7 +3151,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-02-28 13:03     ` Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Yura Sokolov @ 2025-02-28 13:03 UTC (permalink / raw)
  To: [email protected]

17.02.2025 00:27, Alexander Korotkov wrote:
> On Thu, Feb 6, 2025 at 10:31 AM Yura Sokolov <[email protected]> wrote:
>> I briefly looked into patch and have couple of minor remarks:
>>
>> 1. I don't like `palloc` in the `WaitLSNWakeup`. I believe it wont issue
>> problems, but still don't like it. I'd prefer to see local fixed array, say
>> of 16 elements, and loop around remaining function body acting in batch of
>> 16 wakeups. Doubtfully there will be more than 16 waiting clients often,
>> and even then it wont be much heavier than fetching all at once.
> 
> OK, I've refactored this to use static array of 16 size.  palloc() is
> used only if we don't fit static array.

I've rebased patch and:
- fixed compiler warning in wait.c ("maybe uninitialized 'result'").
- made a loop without call to palloc in WaitLSNWakeup. It is with "goto" to
keep indentation, perhaps `do {} while` would be better?

-------
regards
Yura Sokolov aka funny-falcon

Attachments:

  [text/x-patch] v3-0001-Implement-WAIT-FOR-command.patch (45.9K, ../../[email protected]/2-v3-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From fa107e15eab3ec2493f0663f03b563d49979e0b5 Mon Sep 17 00:00:00 2001
From: Yura Sokolov <[email protected]>
Date: Fri, 28 Feb 2025 15:40:18 +0300
Subject: [PATCH v3] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan, Alexander Korotkov
Reviewed-by: Michael Paquier, Peter Eisentraut, Dilip Kumar, Amit Kapila
Reviewed-by: Alexander Lakhin, Bharath Rupireddy, Euler Taveira
Reviewed-by: Heikki Linnakangas, Kyotaro Horiguchi
---
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 347 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 185 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  14 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/045_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 28 files changed, 977 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/045_wait_for_lsn.pl

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..8b585cba751 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY wait               SYSTEM "wait.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..bd14ec00d2d 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &wait;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1b4f21a88d3..e617ae8ead5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 799fc739e18..b9abb696a5e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6219,6 +6220,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52f53fa12e0..b03a39b510d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1831,6 +1832,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..a0f0e480a48
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,347 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+resume:
+	numWakeUpProcs = 0;
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		Assert(numWakeUpProcs < MaxBackends);
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+
+		if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+			break;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	/* Need to recheck if there were more waiters than static array size. */
+	if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+		goto resume;
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 85cfea6fd71..12459111f7c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -63,6 +63,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ce8d1ab8bac..b1c60f60ea7 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -52,4 +52,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..a5f44de1303
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d99c9355c6..11265ae3383 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1114,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16341,6 +16342,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17999,6 +18008,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 174eed70367..27b447b7a7a 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 49204f91a20..dbb613663fa 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -896,6 +897,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index dea24453a6c..61cf02c9527 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1194,10 +1194,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1792,7 +1793,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..d23ac3b0f0b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index e199f071628..3b282043eca 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -88,6 +88,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -346,6 +347,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..0acc61eba5f
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..a7fa00ed41e
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0b208f51bdd..1c3baac08a9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4317,4 +4317,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..6d834f25d2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -493,6 +493,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index cf565452382..a3f66071288 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 057bcde1434..8a8f1f6c427 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -53,6 +53,7 @@ tests += {
       't/042_low_level_backup.pl',
       't/043_no_contrecord_switch.pl',
       't/044_invalidate_inactive_slots.pl',
+      't/045_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/045_wait_for_lsn.pl b/src/test/recovery/t/045_wait_for_lsn.pl
new file mode 100644
index 00000000000..79c2c49b9ce
--- /dev/null
+++ b/src/test/recovery/t/045_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fcb968e1ffe..7b6c30c8d4f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3169,7 +3169,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
@ 2025-02-28 13:55       ` Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Yura Sokolov @ 2025-02-28 13:55 UTC (permalink / raw)
  To: [email protected]; Alexander Korotkov <[email protected]>

28.02.2025 16:03, Yura Sokolov пишет:
> 17.02.2025 00:27, Alexander Korotkov wrote:
>> On Thu, Feb 6, 2025 at 10:31 AM Yura Sokolov <[email protected]> wrote:
>>> I briefly looked into patch and have couple of minor remarks:
>>>
>>> 1. I don't like `palloc` in the `WaitLSNWakeup`. I believe it wont issue
>>> problems, but still don't like it. I'd prefer to see local fixed array, say
>>> of 16 elements, and loop around remaining function body acting in batch of
>>> 16 wakeups. Doubtfully there will be more than 16 waiting clients often,
>>> and even then it wont be much heavier than fetching all at once.
>>
>> OK, I've refactored this to use static array of 16 size.  palloc() is
>> used only if we don't fit static array.
> 
> I've rebased patch and:
> - fixed compiler warning in wait.c ("maybe uninitialized 'result'").
> - made a loop without call to palloc in WaitLSNWakeup. It is with "goto" to
> keep indentation, perhaps `do {} while` would be better?

And fixed:
   'WAIT' is marked as BARE_LABEL in kwlist.h, but it is missing from
gram.y's bare_label_keyword rule

-------
regards
Yura Sokolov aka funny-falcon

Attachments:

  [text/x-patch] v4-0001-Implement-WAIT-FOR-command.patch (46.0K, ../../[email protected]/2-v4-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From d9c44427a4cbecd6dd27edae48ea42d933756ff9 Mon Sep 17 00:00:00 2001
From: Yura Sokolov <[email protected]>
Date: Fri, 28 Feb 2025 15:40:18 +0300
Subject: [PATCH v4] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan, Alexander Korotkov
Reviewed-by: Michael Paquier, Peter Eisentraut, Dilip Kumar, Amit Kapila
Reviewed-by: Alexander Lakhin, Bharath Rupireddy, Euler Taveira
Reviewed-by: Heikki Linnakangas, Kyotaro Horiguchi
---
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 347 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 185 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  15 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/045_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 28 files changed, 978 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/045_wait_for_lsn.pl

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..8b585cba751 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY wait               SYSTEM "wait.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..bd14ec00d2d 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &wait;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1b4f21a88d3..e617ae8ead5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 799fc739e18..b9abb696a5e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6219,6 +6220,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52f53fa12e0..b03a39b510d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1831,6 +1832,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..a0f0e480a48
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,347 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+resume:
+	numWakeUpProcs = 0;
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		Assert(numWakeUpProcs < MaxBackends);
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+
+		if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+			break;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	/* Need to recheck if there were more waiters than static array size. */
+	if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+		goto resume;
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 85cfea6fd71..12459111f7c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -63,6 +63,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ce8d1ab8bac..b1c60f60ea7 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -52,4 +52,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..a5f44de1303
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d99c9355c6..3034573648f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1114,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16341,6 +16342,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17999,6 +18008,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18655,6 +18665,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 174eed70367..27b447b7a7a 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 49204f91a20..dbb613663fa 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -896,6 +897,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index dea24453a6c..61cf02c9527 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1194,10 +1194,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1792,7 +1793,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..d23ac3b0f0b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index e199f071628..3b282043eca 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -88,6 +88,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -346,6 +347,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..0acc61eba5f
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..a7fa00ed41e
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0b208f51bdd..1c3baac08a9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4317,4 +4317,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..6d834f25d2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -493,6 +493,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index cf565452382..a3f66071288 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 057bcde1434..8a8f1f6c427 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -53,6 +53,7 @@ tests += {
       't/042_low_level_backup.pl',
       't/043_no_contrecord_switch.pl',
       't/044_invalidate_inactive_slots.pl',
+      't/045_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/045_wait_for_lsn.pl b/src/test/recovery/t/045_wait_for_lsn.pl
new file mode 100644
index 00000000000..79c2c49b9ce
--- /dev/null
+++ b/src/test/recovery/t/045_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fcb968e1ffe..7b6c30c8d4f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3169,7 +3169,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
@ 2025-03-10 11:30         ` Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-03-10 11:30 UTC (permalink / raw)
  To: Yura Sokolov <[email protected]>; +Cc: [email protected]

On Fri, Feb 28, 2025 at 3:55 PM Yura Sokolov <[email protected]> wrote:
> 28.02.2025 16:03, Yura Sokolov пишет:
> > 17.02.2025 00:27, Alexander Korotkov wrote:
> >> On Thu, Feb 6, 2025 at 10:31 AM Yura Sokolov <[email protected]> wrote:
> >>> I briefly looked into patch and have couple of minor remarks:
> >>>
> >>> 1. I don't like `palloc` in the `WaitLSNWakeup`. I believe it wont issue
> >>> problems, but still don't like it. I'd prefer to see local fixed array, say
> >>> of 16 elements, and loop around remaining function body acting in batch of
> >>> 16 wakeups. Doubtfully there will be more than 16 waiting clients often,
> >>> and even then it wont be much heavier than fetching all at once.
> >>
> >> OK, I've refactored this to use static array of 16 size.  palloc() is
> >> used only if we don't fit static array.
> >
> > I've rebased patch and:
> > - fixed compiler warning in wait.c ("maybe uninitialized 'result'").
> > - made a loop without call to palloc in WaitLSNWakeup. It is with "goto" to
> > keep indentation, perhaps `do {} while` would be better?
>
> And fixed:
>    'WAIT' is marked as BARE_LABEL in kwlist.h, but it is missing from
> gram.y's bare_label_keyword rule

Thank you, Yura.  I've further revised the patch.  Mostly added the
documentation including SQL command reference and few paragraphs in
the high availability chapter explaining the read-your-writes
consistency concept.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v5-0001-Implement-WAIT-FOR-command.patch (55.7K, ../../CAPpHfduRK_wL5w7N+Vgt5f3WHegoFmQ__eDL603Cu39PphPLXQ@mail.gmail.com/2-v5-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 8431a654aa5b872acef2bca7e66dfaff7dd5254d Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 10 Mar 2025 12:59:38 +0200
Subject: [PATCH v5] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 216 +++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 347 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 185 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  15 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/045_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 30 files changed, 1248 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/045_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index acf3ac0601d..ae316b5a0c9 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it possible to address this without switching to the synchronous
+    replication
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..9d6d3175f02
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,216 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>AFTER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait target <acronym>LSN</acronym> to be replayed within the specified timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR ( <replaceable class="parameter">parameter</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    as soon as <parameter>throw</parameter> is not specified or set to true.
+    If <parameter>throw</parameter> is set to false, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      The target log sequence number to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      When specified and greater than zero, the command waits until
+      <parameter>lsn</parameter> is reached or the specified
+      <parameter>timeout</parameter> has elapsed.  Must be a non-negative
+      integer, the default is zero.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">throw</replaceable></term>
+    <listitem>
+     <para>
+      Specify whether to throw an error in the case of timeout or
+      running on the primary.  The valid values are <literal>true</literal>
+      and <literal>false</literal>.  The default is <literal>true</literal>.
+      When set to <literal>false</literal> the status can be get from the
+      return `value.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return values</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">success</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">not in recovery</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this function execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out as soon as <parameter>throw</parameter> is true.
+    However, if <function>pg_wal_replay_wait</function> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+   </programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+   </programlisting>
+  </para>
+
+  <para>
+    It may also happen that target <parameter>lsn</parameter> is not reached
+    within the timeout.  In that case the error is thrown.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20', TIMEOUT '100';
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+   </programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>throw</parameter> set to false.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20', TIMEOUT '100', THROW 'false';
+ RESULT STATUS
+---------------
+ timeout
+(1 row)
+   </programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1b4f21a88d3..e617ae8ead5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 799fc739e18..b9abb696a5e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6219,6 +6220,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a829a055a97..1beb3999769 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1831,6 +1832,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..a0f0e480a48
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,347 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+resume:
+	numWakeUpProcs = 0;
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		Assert(numWakeUpProcs < MaxBackends);
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+
+		if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+			break;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	/* Need to recheck if there were more waiters than static array size. */
+	if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+		goto resume;
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 85cfea6fd71..12459111f7c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -63,6 +63,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ce8d1ab8bac..b1c60f60ea7 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -52,4 +52,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..a5f44de1303
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 271ae26cbaf..e4916148d02 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1114,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16369,6 +16370,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -18027,6 +18036,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18683,6 +18693,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 174eed70367..27b447b7a7a 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 749a79d48ef..1a99e98f55b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -896,6 +897,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index dea24453a6c..61cf02c9527 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1194,10 +1194,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1792,7 +1793,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..d23ac3b0f0b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 3c594415bfd..5849967882e 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -88,6 +88,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -346,6 +347,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..0acc61eba5f
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..a7fa00ed41e
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 23c9e3c5abf..dffa714e2c8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4319,4 +4319,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..6d834f25d2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -493,6 +493,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index cf565452382..a3f66071288 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 057bcde1434..8a8f1f6c427 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -53,6 +53,7 @@ tests += {
       't/042_low_level_backup.pl',
       't/043_no_contrecord_switch.pl',
       't/044_invalidate_inactive_slots.pl',
+      't/045_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/045_wait_for_lsn.pl b/src/test/recovery/t/045_wait_for_lsn.pl
new file mode 100644
index 00000000000..79c2c49b9ce
--- /dev/null
+++ b/src/test/recovery/t/045_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9840060997f..5ce3d36ae6d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3175,7 +3175,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-03-12 14:44           ` Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-03-16 13:32             ` Re: Implement waiting for wal lsn replay: reloaded vignesh C <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Yura Sokolov @ 2025-03-12 14:44 UTC (permalink / raw)
  To: [email protected]

10.03.2025 14:30, Alexander Korotkov пишет:
> On Fri, Feb 28, 2025 at 3:55 PM Yura Sokolov <[email protected]> wrote:
>> 28.02.2025 16:03, Yura Sokolov пишет:
>>> 17.02.2025 00:27, Alexander Korotkov wrote:
>>>> On Thu, Feb 6, 2025 at 10:31 AM Yura Sokolov <[email protected]> wrote:
>>>>> I briefly looked into patch and have couple of minor remarks:
>>>>>
>>>>> 1. I don't like `palloc` in the `WaitLSNWakeup`. I believe it wont issue
>>>>> problems, but still don't like it. I'd prefer to see local fixed array, say
>>>>> of 16 elements, and loop around remaining function body acting in batch of
>>>>> 16 wakeups. Doubtfully there will be more than 16 waiting clients often,
>>>>> and even then it wont be much heavier than fetching all at once.
>>>>
>>>> OK, I've refactored this to use static array of 16 size.  palloc() is
>>>> used only if we don't fit static array.
>>>
>>> I've rebased patch and:
>>> - fixed compiler warning in wait.c ("maybe uninitialized 'result'").
>>> - made a loop without call to palloc in WaitLSNWakeup. It is with "goto" to
>>> keep indentation, perhaps `do {} while` would be better?
>>
>> And fixed:
>>    'WAIT' is marked as BARE_LABEL in kwlist.h, but it is missing from
>> gram.y's bare_label_keyword rule
> 
> Thank you, Yura.  I've further revised the patch.  Mostly added the
> documentation including SQL command reference and few paragraphs in
> the high availability chapter explaining the read-your-writes
> consistency concept.

Good day, Alexander.

Looking "for the last time" to the patch I found there remains
`pg_wal_replay_wait` function in documentation and one comment.
So I fixed it in documentation, and removed sentence from comment.

Otherwise v6 is just rebased v5.

-------
regards
Yura Sokolov aka funny-falcon

Attachments:

  [text/x-patch] v6-0001-Implement-WAIT-FOR-command.patch (55.7K, ../../[email protected]/2-v6-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 80b4cb8c0ac75168ab1fce55feccc4f08f32ce34 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 10 Mar 2025 12:59:38 +0200
Subject: [PATCH v6] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Yura Sokolov <[email protected]>
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 216 +++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 347 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 184 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  15 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 ++
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  89 +++++
 src/include/commands/wait.h                   |  21 ++
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/045_wait_for_lsn.pl       | 217 +++++++++++
 src/tools/pgindent/typedefs.list              |   4 +
 30 files changed, 1247 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/045_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index acf3ac0601d..ae316b5a0c9 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it possible to address this without switching to the synchronous
+    replication
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..2352ae9493f
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,216 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>AFTER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait target <acronym>LSN</acronym> to be replayed within the specified timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR ( <replaceable class="parameter">parameter</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    as soon as <parameter>throw</parameter> is not specified or set to true.
+    If <parameter>throw</parameter> is set to false, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      The target log sequence number to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      When specified and greater than zero, the command waits until
+      <parameter>lsn</parameter> is reached or the specified
+      <parameter>timeout</parameter> has elapsed.  Must be a non-negative
+      integer, the default is zero.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">throw</replaceable></term>
+    <listitem>
+     <para>
+      Specify whether to throw an error in the case of timeout or
+      running on the primary.  The valid values are <literal>true</literal>
+      and <literal>false</literal>.  The default is <literal>true</literal>.
+      When set to <literal>false</literal> the status can be get from the
+      return `value.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return values</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">success</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">not in recovery</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this function execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out as soon as <parameter>throw</parameter> is true.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+   </programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+   </programlisting>
+  </para>
+
+  <para>
+    It may also happen that target <parameter>lsn</parameter> is not reached
+    within the timeout.  In that case the error is thrown.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20', TIMEOUT '100';
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+   </programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>throw</parameter> set to false.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20', TIMEOUT '100', THROW 'false';
+ RESULT STATUS
+---------------
+ timeout
+(1 row)
+   </programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1b4f21a88d3..e617ae8ead5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2826,6 +2827,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 799fc739e18..b9abb696a5e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6219,6 +6220,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a829a055a97..1beb3999769 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1831,6 +1832,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..a0f0e480a48
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,347 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+resume:
+	numWakeUpProcs = 0;
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	/*
+	 * Iterate the pairing heap of waiting processes till we find LSN not yet
+	 * replayed.  Record the process numbers to wake up, but to avoid holding
+	 * the lock for too long, send the wakeups only after releasing the lock.
+	 */
+	while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+		if (!XLogRecPtrIsInvalid(currentLSN) &&
+			procInfo->waitLSN > currentLSN)
+			break;
+
+		Assert(numWakeUpProcs < MaxBackends);
+		wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+		(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+		procInfo->inHeap = false;
+
+		if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+			break;
+	}
+
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+
+	/*
+	 * Set latches for processes, whose waited LSNs are already replayed. As
+	 * the time consuming operations, we do it this outside of WaitLSNLock.
+	 * This is  actually fine because procLatch isn't ever freed, so we just
+	 * can potentially set the wrong process' (or no process') latch.
+	 */
+	for (i = 0; i < numWakeUpProcs; i++)
+		SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	/* Need to recheck if there were more waiters than static array size. */
+	if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+		goto resume;
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the postmaster dies or
+ * timeout happens.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 85cfea6fd71..12459111f7c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -63,6 +63,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ce8d1ab8bac..b1c60f60ea7 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -52,4 +52,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..d95782ddaf8
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		char	   *name = str_tolower(defel->defname, strlen(defel->defname),
+									   DEFAULT_COLLATION_OID);
+
+		if (strcmp(name, "lsn") == 0)
+		{
+			lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+												  CStringGetDatum(strVal(defel->arg))));
+		}
+		else if (strcmp(name, "timeout") == 0)
+		{
+			timeout = pg_strtoint64(strVal(defel->arg));
+		}
+		else if (strcmp(name, "throw") == 0)
+		{
+			throw = DatumGetBool(DirectFunctionCall1(boolin,
+													 CStringGetDatum(strVal(defel->arg))));
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("wrong wait argument: %s",
+							defel->defname)));
+		}
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 271ae26cbaf..e4916148d02 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1114,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16369,6 +16370,14 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR generic_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
 
 /*
  * Aggregate decoration clauses
@@ -18027,6 +18036,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18683,6 +18693,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 174eed70367..27b447b7a7a 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -340,6 +342,7 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 749a79d48ef..1a99e98f55b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -896,6 +897,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index dea24453a6c..61cf02c9527 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1194,10 +1194,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1792,7 +1793,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..d23ac3b0f0b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 3c594415bfd..5849967882e 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -88,6 +88,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -346,6 +347,7 @@ WALSummarizer	"Waiting to read or update WAL summarization state."
 DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..0acc61eba5f
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..a7fa00ed41e
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 23c9e3c5abf..dffa714e2c8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4319,4 +4319,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..6d834f25d2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -493,6 +493,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index cf565452382..a3f66071288 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -83,3 +83,4 @@ PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
+PG_LWLOCK(53, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 057bcde1434..8a8f1f6c427 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -53,6 +53,7 @@ tests += {
       't/042_low_level_backup.pl',
       't/043_no_contrecord_switch.pl',
       't/044_invalidate_inactive_slots.pl',
+      't/045_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/045_wait_for_lsn.pl b/src/test/recovery/t/045_wait_for_lsn.pl
new file mode 100644
index 00000000000..79c2c49b9ce
--- /dev/null
+++ b/src/test/recovery/t/045_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}', TIMEOUT '1000000';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}', TIMEOUT '10';");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}', TIMEOUT '1000';",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}', TIMEOUT '10', THROW 'false';]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index dfe2690bdd3..5377d6208e1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3177,7 +3177,11 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
@ 2025-03-13 14:15             ` Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Tomas Vondra @ 2025-03-13 14:15 UTC (permalink / raw)
  To: Yura Sokolov <[email protected]>; [email protected]

Hi,

I did a quick look at this patch. I haven't found any correctness
issues, but I have some general review comments and questions about the
grammar / syntax.

1) The sgml docs don't really show the syntax very nicely, it only shows
this at the beginning of wait_for.sgml:

   WAIT FOR ( <replaceable class="parameter">parameter</replaceable>
'<replaceable class="parameter">value</replaceable>' [, ... ] ) ]

I kinda understand this comes from using the generic option list (I'll
get to that shortly), but I think it'd be much better to actually show
the "full" syntax here, instead of leaving the "parameters" to later.


2) The syntax description suggests "(" and ")" are required, but that
does not seem to be the case - in fact, it's not even optional, and when
I try using that, I get syntax error.


3) I have my doubts about using the generic_option_list for this. Yes, I
understand this allows using fewer reserved keywords, but it leads to
some weirdness and I'm not sure it's worth it. Not sure what the right
trade off is here.

Anyway, some examples of the weird stuff implied by this approach:

- it forces "," between the options, which is a clear difference from
what we do for every other command

- it forces everything to be a string, i.e. you can' say "TIMEOUT 10",
it has to be "TIMEOUT '10'"

I don't have a very strong opinion on this, but the result seems a bit
strange to me.


4) I'm not sure I understand the motivation of the "throw false" mode,
and I'm not sure I understand this description in the sgml docs:

    On timeout, or if the server is promoted before
    <parameter>lsn</parameter> is reached, an error is emitted,
    as soon as <parameter>throw</parameter> is not specified or set to
    true.
    If <parameter>throw</parameter> is set to false, then the command
    doesn't throw errors.

I find it a bit confusing. What is the use case for this mode?


5) One place in the docs says:

      The target log sequence number to wait for.

   Thie is literally the only place using "log sequence number" in our
   code base, I'd just use "LSN" just like every other place.


6) The docs for the TIMEOUT parameter say this:

   <varlistentry>
    <term><replaceable class="parameter">timeout</replaceable></term>
    <listitem>
     <para>
      When specified and greater than zero, the command waits until
      <parameter>lsn</parameter> is reached or the specified
      <parameter>timeout</parameter> has elapsed.  Must be a non-
      negative integer, the default is zero.
     </para>
    </listitem>
   </varlistentry>

   That doesn't say what unit does the option use. Is is seconds,
   milliseconds or what?

   In fact, it'd be nice to let users specify that in the value, similar
   to other options (e.g. SET statement_timeout = '10s').


7) One place in the docs says this:

    That is, after this function execution, the value returned by
    <function>pg_last_wal_replay_lsn</function> should be greater ...

  I think the reference to "function execution" is obsolete?


8) I find this confusing:

    However, if <command>WAIT FOR</command> is
    called on primary promoted from standby and <literal>lsn</literal>
    was already replayed, then the <command>WAIT FOR</command> command
    just exits immediately.

  Does this mean running the WAIT command on a primary (after it was
  already promoted) will exit immediately? Why does it matter that it
  was promoted from a standby? Shouldn't it exit immediately even for
  a standalone instance?


9) xlogwait.c

I think this should start with a basic "design" description of how the
wait is implemented, in a comment at the top of the file. That is, what
we keep in the shared memory, what happens during a wait, how it uses
the pairing heap, etc. After reading this comment I should understand
how it all fits together.


10) WaitForLSNReplay / WaitLSNWakeup

I think the function comment should document the important stuff (e.g.
return values for various situations, how it groups waiters into chunks
of 16 elements during wakeup, ...).


11) WaitLSNProcInfo / WaitLSNState

Does this need to be exposed in xlogwait.h? These structs seem private
to xlogwait.c, so maybe declare it there?


regards

-- 
Tomas Vondra






^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
@ 2025-04-29 11:27               ` Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-04-29 11:27 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Yura Sokolov <[email protected]>; [email protected]

Hi, Tomas.

Thank you so much for your review!  Please find the revised patchset.

On Thu, Mar 13, 2025 at 4:15 PM Tomas Vondra <[email protected]> wrote:
> I did a quick look at this patch. I haven't found any correctness
> issues, but I have some general review comments and questions about the
> grammar / syntax.
>
> 1) The sgml docs don't really show the syntax very nicely, it only shows
> this at the beginning of wait_for.sgml:
>
>    WAIT FOR ( <replaceable class="parameter">parameter</replaceable>
> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
>
> I kinda understand this comes from using the generic option list (I'll
> get to that shortly), but I think it'd be much better to actually show
> the "full" syntax here, instead of leaving the "parameters" to later.

Sounds reasonable, changed to show the full syntax in the synopsis.

> 2) The syntax description suggests "(" and ")" are required, but that
> does not seem to be the case - in fact, it's not even optional, and when
> I try using that, I get syntax error.

Good catch, fixed.

> 3) I have my doubts about using the generic_option_list for this. Yes, I
> understand this allows using fewer reserved keywords, but it leads to
> some weirdness and I'm not sure it's worth it. Not sure what the right
> trade off is here.
>
> Anyway, some examples of the weird stuff implied by this approach:
>
> - it forces "," between the options, which is a clear difference from
> what we do for every other command
>
> - it forces everything to be a string, i.e. you can' say "TIMEOUT 10",
> it has to be "TIMEOUT '10'"
>
> I don't have a very strong opinion on this, but the result seems a bit
> strange to me.

I've improved the syntax.  I still tried to keep the number of new
keywords and grammar rules minimal.  That leads to moving some parser
login into wait.c.  This is probably a bit awkward, but saves our
grammar from bloat.  Let me know what do you think about this
approach.

> 4) I'm not sure I understand the motivation of the "throw false" mode,
> and I'm not sure I understand this description in the sgml docs:
>
>     On timeout, or if the server is promoted before
>     <parameter>lsn</parameter> is reached, an error is emitted,
>     as soon as <parameter>throw</parameter> is not specified or set to
>     true.
>     If <parameter>throw</parameter> is set to false, then the command
>     doesn't throw errors.
>
> I find it a bit confusing. What is the use case for this mode?

The idea here is that application could do some handling of these
errors without having to parse the error messages (parsing error
messages is inconvenient because of localization etc).

> 5) One place in the docs says:
>
>       The target log sequence number to wait for.
>
>    Thie is literally the only place using "log sequence number" in our
>    code base, I'd just use "LSN" just like every other place.

OK fixed.

> 6) The docs for the TIMEOUT parameter say this:
>
>    <varlistentry>
>     <term><replaceable class="parameter">timeout</replaceable></term>
>     <listitem>
>      <para>
>       When specified and greater than zero, the command waits until
>       <parameter>lsn</parameter> is reached or the specified
>       <parameter>timeout</parameter> has elapsed.  Must be a non-
>       negative integer, the default is zero.
>      </para>
>     </listitem>
>    </varlistentry>
>
>    That doesn't say what unit does the option use. Is is seconds,
>    milliseconds or what?
>
>    In fact, it'd be nice to let users specify that in the value, similar
>    to other options (e.g. SET statement_timeout = '10s').

The default unit of milliseconds is specified.  Also, an alternative
way to specify timeout is now supported.  Timeout might be a string
literal consisting of numeric and unit specifier.

> 7) One place in the docs says this:
>
>     That is, after this function execution, the value returned by
>     <function>pg_last_wal_replay_lsn</function> should be greater ...
>
>   I think the reference to "function execution" is obsolete?

Actually, this is just the function, which reports current replay LSN,
not function introduced by previous version of this patch.  We refer
it to just express the constraint that LSN must be replayed after
execution of the command.

> 8) I find this confusing:
>
>     However, if <command>WAIT FOR</command> is
>     called on primary promoted from standby and <literal>lsn</literal>
>     was already replayed, then the <command>WAIT FOR</command> command
>     just exits immediately.
>
>   Does this mean running the WAIT command on a primary (after it was
>   already promoted) will exit immediately? Why does it matter that it
>   was promoted from a standby? Shouldn't it exit immediately even for
>   a standalone instance?

I think the previous sentence should give an idea that otherwise error
gets thrown.  That also happens immediately for sure.

> 9) xlogwait.c
>
> I think this should start with a basic "design" description of how the
> wait is implemented, in a comment at the top of the file. That is, what
> we keep in the shared memory, what happens during a wait, how it uses
> the pairing heap, etc. After reading this comment I should understand
> how it all fits together.

OK, I've added the header comment.

> 10) WaitForLSNReplay / WaitLSNWakeup
>
> I think the function comment should document the important stuff (e.g.
> return values for various situations, how it groups waiters into chunks
> of 16 elements during wakeup, ...).

Revised header comments for those functions too.

> 11) WaitLSNProcInfo / WaitLSNState
>
> Does this need to be exposed in xlogwait.h? These structs seem private
> to xlogwait.c, so maybe declare it there?

Hmm, I don't remember why I moved them to xlogwait.h.  OK, moved them
back to xlogwait.c.


------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/x-patch] v6-0001-Implement-WAIT-FOR-command.patch (59.9K, ../../CAPpHfdt5VCM1DoodvVoiRUaoRuXrNEzAmrMQ-eLa0E7wByXaKw@mail.gmail.com/2-v6-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 11f1b1db81ff323354035dba34a34f5ac55177a3 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 10 Mar 2025 12:59:38 +0200
Subject: [PATCH v6] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://postgr.es/m/eb12f9b03851bb2583adab5df9579b4b%40postgrespro.ru
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 226 +++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 435 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 235 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  29 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  41 ++
 src/include/commands/wait.h                   |  21 +
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/046_wait_for_lsn.pl       | 217 +++++++++
 src/tools/pgindent/typedefs.list              |   5 +
 30 files changed, 1363 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/046_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..e29141c0538 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it possible to address this without switching to the synchronous
+    replication
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..ff3f309bc7c
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,226 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>AFTER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait target <acronym>LSN</acronym> to be replayed within the specified timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR ( <replaceable class="parameter">option</replaceable> [, ... ] ) ]
+ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WITH ] <replaceable class="parameter">option</replaceable> [ ... ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+      LSN '<replaceable class="parameter">lsn</replaceable>'
+    | TIMEOUT <replaceable class="parameter">timeout</replaceable>
+    | NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    as soon as <literal>NO_THROW</literal> is not specified.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Options</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>LSN</literal> '<replaceable class="parameter">lsn</replaceable>'</term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>TIMEOUT</literal> <replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      When specified and <parameter>timeout</parameter> is greater than zero,
+      the command waits until <parameter>lsn</parameter> is reached or
+      the specified <parameter>timeout</parameter> has elapsed.
+     </para>
+     <para>
+      The <parameter>timeout</parameter> might be given as integer number of
+      milliseconds.  Also it might be given as string literal with
+      integer number of milliseconds or a number with unit
+      (see <xref linkend="config-setting-names-values"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NO_THROW</literal></term>
+    <listitem>
+     <para>
+      Specify to not throw an error in the case of timeout or
+      running on the primary.  In this case the result status can be get from
+      the return value.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return values</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">success</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">not in recovery</replaceable></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this function execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out as soon as <parameter>NO_THROW</parameter> is not specified.
+    However, if <function>pg_wal_replay_wait</function> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+   </programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+   </programlisting>
+  </para>
+
+  <para>
+    It may also happen that target <parameter>lsn</parameter> is not reached
+    within the timeout.  In that case the error is thrown.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT '0.1 s';
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+   </programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT 100 NO_THROW;
+ RESULT STATUS
+---------------
+ timeout
+(1 row)
+   </programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b885513f765..511e5531fb8 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2831,6 +2832,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 2d4c346473b..a0c98d9e801 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6361,6 +6362,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 6ce979f2d8b..2097271b2f8 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1836,6 +1837,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..c2aee2d41f0
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,435 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for the replay of the given LSN on a
+ *		physical standby.  The core idea is very small: every backend that
+ *		wants to wait publishes the LSN it needs to the shared memory, and
+ *		the startup process wakes it once that LSN has been replayed.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by a startup
+ *		process, timeout is reached, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
+ *
+ *		After replaying a WAL record, the startup process first performs a
+ *		fast path check minWaitedLSN > replayLSN.  If this check is negative,
+ *		it checks waitersHeap and wakes up the backend whose awaited LSNs
+ *		are reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0, MaxBackends * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitLSN->minWaitedLSN according to the current state of
+ * waitLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the pairing heap of waiting processes till we find LSN not
+		 * yet replayed.  Record the process numbers to wake up, but to avoid
+		 * holding the lock for too long, send the wakeups only after
+		 * releasing the lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) &&
+				procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < MaxBackends);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+			procInfo->inHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN();
+
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already replayed.
+		 * As the time consuming operations, we do it this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+		/* Need to recheck if there were more waiters than static array size. */
+	}
+	while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, a timeout happens, the
+ * replica gets promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.  Returns
+ * WAIT_LSN_RESULT_TIMEOUT if the timeout was reached before the target LSN
+ * replayed.  Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..784c779a252
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,235 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "nodes/print.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+typedef enum
+{
+	WaitStmtParamNone,
+	WaitStmtParamTimeout,
+	WaitStmtParamLSN
+} WaitStmtParam;
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	WaitStmtParam curParam = WaitStmtParamNone;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	foreach_ptr(Node, option, stmt->options)
+	{
+		if (IsA(option, String))
+		{
+			String	   *str = castNode(String, option);
+			char	   *name = str_tolower(str->sval, strlen(str->sval),
+										   DEFAULT_COLLATION_OID);
+
+			if (curParam != WaitStmtParamNone)
+				elog(ERROR, "Unexpected param");
+
+			if (strcmp(name, "lsn") == 0)
+				curParam = WaitStmtParamLSN;
+			else if (strcmp(name, "timeout") == 0)
+				curParam = WaitStmtParamTimeout;
+			else if (strcmp(name, "no_throw") == 0)
+				throw = false;
+			else
+				elog(ERROR, "Unexpected param");
+
+		}
+		else if (IsA(option, Integer))
+		{
+			Integer    *intVal = castNode(Integer, option);
+
+			if (curParam != WaitStmtParamTimeout)
+				elog(ERROR, "Unexpected integer");
+
+			timeout = intVal->ival;
+
+			curParam = WaitStmtParamNone;
+		}
+		else if (IsA(option, A_Const))
+		{
+			A_Const    *constVal = castNode(A_Const, option);
+			String	   *str = &constVal->val.sval;
+
+			if (curParam != WaitStmtParamLSN &&
+				curParam != WaitStmtParamTimeout)
+				elog(ERROR, "Unexpected string");
+
+			if (curParam == WaitStmtParamLSN)
+			{
+				lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+													  CStringGetDatum(str->sval)));
+			}
+			else if (curParam == WaitStmtParamTimeout)
+			{
+				const char *hintmsg;
+				double		result;
+
+				if (!parse_real(str->sval, &result, GUC_UNIT_MS, &hintmsg))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for timeout option: \"%s\"",
+									str->sval),
+							 hintmsg ? errhint("%s", _(hintmsg)) : 0));
+				}
+				timeout = (int64) result;
+			}
+
+			curParam = WaitStmtParamNone;
+		}
+
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "RESULT STATUS",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3c4268b271a..5ff7157a12a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -303,7 +303,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -672,6 +672,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <node>	wait_option
+%type <list>	wait_option_list
+
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -786,7 +789,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1114,6 +1117,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16364,6 +16368,25 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR wait_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
+
+wait_option_list:
+			wait_option						{ $$ = list_make1($1); }
+			| wait_option_list wait_option	{ $$ = lappend($1, $2); }
+			;
+
+wait_option: ColLabel						{ $$ = (Node *) makeString($1); }
+			 | NumericOnly					{ $$ = (Node *) $1; }
+			 | Sconst						{ $$ = (Node *) makeStringConst($1, @1); }
+
+		;
 
 /*
  * Aggregate decoration clauses
@@ -18023,6 +18046,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18680,6 +18704,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 00c76d05356..87411aece47 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -152,6 +153,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
 	size = add_size(size, MemoryContextReportingShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -346,6 +348,7 @@ CreateOrAttachShmemStructs(void)
 	InjectionPointShmemInit();
 	AioShmemInit();
 	MemoryContextReportingShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index f194e6b3dcc..c966acdbff0 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -948,6 +949,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8164d0fbb4f..f4d37c0bfc2 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1195,10 +1195,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1793,7 +1794,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..d23ac3b0f0b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1065,6 +1067,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2067,6 +2075,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2122,6 +2133,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3099,6 +3113,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3697,6 +3715,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 930321905f1..164a16bc5d8 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for a replay of the particular WAL position on the physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -353,6 +354,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..15bddd9dba3
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..b44c37aa4db
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4610fc61293..d06104d40ac 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4326,4 +4326,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..dec7ec6e5ec 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -494,6 +494,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index a9681738146..eb9de7dae00 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -84,3 +84,4 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index cb983766c67..31b1e9bffcf 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -54,6 +54,7 @@ tests += {
       't/043_no_contrecord_switch.pl',
       't/044_invalidate_inactive_slots.pl',
       't/045_archive_restartpoint.pl',
+      't/046_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/046_wait_for_lsn.pl b/src/test/recovery/t/046_wait_for_lsn.pl
new file mode 100644
index 00000000000..f9446cce3f9
--- /dev/null
+++ b/src/test/recovery/t/046_wait_for_lsn.pl
@@ -0,0 +1,217 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' TIMEOUT '1d';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' TIMEOUT 10;");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' TIMEOUT 1000;",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' TIMEOUT '0.1 s' NO_THROW;]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' TIMEOUT 10 NO_THROW;]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' TIMEOUT '10ms' NO_THROW;]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e5879e00dff..be191d3e2d9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3236,7 +3236,12 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
+WaitStmtParam
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-08-05 13:47                 ` Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 06:54                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-08-05 13:47 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

On 2025-Apr-29, Alexander Korotkov wrote:

> > 11) WaitLSNProcInfo / WaitLSNState
> >
> > Does this need to be exposed in xlogwait.h? These structs seem private
> > to xlogwait.c, so maybe declare it there?
> 
> Hmm, I don't remember why I moved them to xlogwait.h.  OK, moved them
> back to xlogwait.c.

This change made the code no longer compile, because
WaitLSNState->minWaitedLSN is used in xlogrecovery.c which no longer has
access to the field definition.  A rebased version with that change
reverted is attached.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
Thou shalt study thy libraries and strive not to reinvent them without
cause, that thy code may be short and readable and thy days pleasant
and productive. (7th Commandment for C Programmers)


^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2025-08-07 15:00                   ` Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-08-07 15:00 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; Álvaro Herrera <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

Thanks for working on this.

I’ve just come across this thread and haven’t had a chance to dig into
the patch yet, but I’m keen to review it soon. In the meantime, I have
a quick question: is WAIT FOR REPLY intended mainly for user-defined
functions, or can internal code invoke it as well?

During a recent performance run [1] I noticed heavy polling in
read_local_xlog_page_guts(). Heikki’s comment from a few months ago
also hints that we could replace this check–sleep–repeat loop with the
condition-variable (CV) infrastructure used by walsender:

/*
 * Loop waiting for xlog to be available if necessary
 *
 * TODO: The walsender has its own version of this function, which uses a
 * condition variable to wake up whenever WAL is flushed. We could use the
 * same infrastructure here, instead of the check/sleep/repeat style of
 * loop.
 */

Because read_local_xlog_page_guts() waits for a specific flush or
replay LSN, polling becomes inefficient when the wait is long. I built
a POC patch that swaps polling for CVs, but a single global CV (or
even separate “flush” and “replay” CVs) isn’t ideal:

The wake-up routines don’t know which LSN each waiter cares about, so
they’d have to broadcast on every flush/replay. Caching the minimum
outstanding LSN could reduce spuriously awakened waiters, yet wouldn’t
eliminate them—multiple backends might wait for different LSNs
simultaneously. A more precise solution would require a request queue
that maps waiters to target LSNs and issues targeted wake-ups, adding
complexity.

Walsender accepts the potential broadcast overhead by using two cvs
for different waiters, so it might be acceptable for
read_local_xlog_page_guts() as well. However, if WAIT FOR REPLY
becomes available to backend code, we might leverage it to eliminate
the polling for waiting replay in read_local_xlog_page_guts() without
introducing a bespoke dispatcher. I’d appreciate any thoughts on
whether that use case is in scope.

Best,
Xuneng

[1] https://www.postgresql.org/message-id/[email protected]...





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-08-08 07:08                     ` Alexander Korotkov <[email protected]>
  2025-08-09 10:52                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-09 11:27                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 3 replies; 527+ messages in thread

From: Alexander Korotkov @ 2025-08-08 07:08 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi, Xuneng Zhou!

On Thu, Aug 7, 2025 at 6:01 PM Xuneng Zhou <[email protected]> wrote:
> Thanks for working on this.
>
> I’ve just come across this thread and haven’t had a chance to dig into
> the patch yet, but I’m keen to review it soon.

Great.  Thank you for your attention to this patch.  I appreciate your
intention to review it.

> In the meantime, I have
> a quick question: is WAIT FOR REPLY intended mainly for user-defined
> functions, or can internal code invoke it as well?

Currently, WaitForLSNReplay() is assumed to only be called from
backend, as corresponding shmem is allocated only per-backend.  But
there is absolutely no problem to tweak the patch to allocate shmem
for every Postgres process.  This would enable to call
WaitForLSNReplay() wherever it is needed.  There is only no problem to
extend this approach to support other kinds of LSNs not just replay
LSN.


> During a recent performance run [1] I noticed heavy polling in
> read_local_xlog_page_guts(). Heikki’s comment from a few months ago
> also hints that we could replace this check–sleep–repeat loop with the
> condition-variable (CV) infrastructure used by walsender:
>
> /*
>  * Loop waiting for xlog to be available if necessary
>  *
>  * TODO: The walsender has its own version of this function, which uses a
>  * condition variable to wake up whenever WAL is flushed. We could use the
>  * same infrastructure here, instead of the check/sleep/repeat style of
>  * loop.
>  */
>
> Because read_local_xlog_page_guts() waits for a specific flush or
> replay LSN, polling becomes inefficient when the wait is long. I built
> a POC patch that swaps polling for CVs, but a single global CV (or
> even separate “flush” and “replay” CVs) isn’t ideal:
>
> The wake-up routines don’t know which LSN each waiter cares about, so
> they’d have to broadcast on every flush/replay. Caching the minimum
> outstanding LSN could reduce spuriously awakened waiters, yet wouldn’t
> eliminate them—multiple backends might wait for different LSNs
> simultaneously. A more precise solution would require a request queue
> that maps waiters to target LSNs and issues targeted wake-ups, adding
> complexity.
>
> Walsender accepts the potential broadcast overhead by using two cvs
> for different waiters, so it might be acceptable for
> read_local_xlog_page_guts() as well. However, if WAIT FOR REPLY
> becomes available to backend code, we might leverage it to eliminate
> the polling for waiting replay in read_local_xlog_page_guts() without
> introducing a bespoke dispatcher. I’d appreciate any thoughts on
> whether that use case is in scope.

This looks like a great new use-case for facilities developed in this
patch!  I'll remove the restriction to use WaitForLSNReplay() only in
backend.  I think you can write a patch with additional pairing heap
for flush LSN and include that into thread about
read_local_xlog_page_guts() optimization.  Let me know if you need any
assistance.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-08-09 10:52                       ` Xuneng Zhou <[email protected]>
  2 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-08-09 10:52 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi Alexander!

> > In the meantime, I have
> > a quick question: is WAIT FOR REPLY intended mainly for user-defined
> > functions, or can internal code invoke it as well?
>
> Currently, WaitForLSNReplay() is assumed to only be called from
> backend, as corresponding shmem is allocated only per-backend.  But
> there is absolutely no problem to tweak the patch to allocate shmem
> for every Postgres process.  This would enable to call
> WaitForLSNReplay() wherever it is needed.  There is only no problem to
> extend this approach to support other kinds of LSNs not just replay
> LSN.

Thanks for extending the functionality of the Wait For Replay patch!

> This looks like a great new use-case for facilities developed in this
> patch!  I'll remove the restriction to use WaitForLSNReplay() only in
> backend.  I think you can write a patch with additional pairing heap
> for flush LSN and include that into thread about
> read_local_xlog_page_guts() optimization.  Let me know if you need any
> assistance.

This could be a more elegant approach which would solve the polling
issue well. I'll prepare a follow-up patch for it.

Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-08-09 11:27                       ` Xuneng Zhou <[email protected]>
  2 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-08-09 11:27 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

> On Thu, Aug 7, 2025 at 6:01 PM Xuneng Zhou <[email protected]> wrote:
> > Thanks for working on this.
> >
> > I’ve just come across this thread and haven’t had a chance to dig into
> > the patch yet, but I’m keen to review it soon.
>
> Great.  Thank you for your attention to this patch.  I appreciate your
> intention to review it.

I did a quick pass over v7. There are a few thoughts to share—mostly
around documentation, build, and tests, plus some minor nits. The core
logic looks solid to me. I’ll take a deeper look as I work on a
follow‑up patch to add waiting for flush LSNs. And the patch seems to
need rebase; it can't be applied to HEAD cleanly for now.

Build
1) Consider adding a comma in `src/test/recovery/meson.build` after
`'t/048_vacuum_horizon_floor.pl'` so the list remains valid.

Core code
2) It may be safer for `WaitLSNWakeup()` to assert against the stack array size:
) Perhaps `Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);`
rather than `MaxBackends`.
For option parsing UX in `wait.c`, we might prefer:
3) Using `ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(...)))` instead of `elog(ERROR, ...)` for consistency and
translatability.
4) Explicitly rejecting duplicate `LSN`/`TIMEOUT` options with a syntax error.
5) The result column label could align better with other utility
outputs if shortened to `status` (lowercase, no space).
6) After `parse_real()`, it could help to validate/clamp the timeout
to avoid overflow when converting to `int64` and when passing a `long`
to `WaitLatch()`.
7) If `nodes/print.h` in `src/backend/commands/wait.c` isn’t used, we
might drop the include.
8) A couple of comment nits: “do it this outside” → “do this outside”.

Tests
9) We might consider adding cases for:
- Negative `TIMEOUT` (to exercise the error path).
- Syntax errors (unknown option; duplicate `LSN`/`TIMEOUT`; missing `LSN`).

Documentation
`doc/src/sgml/ref/wait_for.sgml`
10) The index term could be updated to `<primary>WAIT FOR</primary>`.
11) The synopsis might read more clearly as:
- WAIT FOR LSN '<lsn>' [ TIMEOUT <milliseconds |
'duration-with-units'> ] [ NO_THROW ]
12) The purpose line might be smoother as “wait for a target LSN to be
replayed, optionally with a timeout”.
13) Return values might use `<literal>` for `success`, `timeout`, `not
in recovery`.
14) Consistently calling this a “command” (rather than
function/procedure) could reduce confusion.
15) The example text might read more cleanly as “If the target LSN is
not reached before the timeout …”.

`doc/src/sgml/high-availability.sgml`
16) The sentence could read “However, it is possible to address this
without switching to synchronous replication.”

`src/backend/utils/activity/wait_event_names.txt`
17) The description for `WAIT_FOR_WAL_REPLAY` might be clearer as
“Waiting for WAL replay to reach a target LSN on a standby.”

Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-08-27 15:54                       ` Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-08-27 15:54 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi all,

I did a rebase for the patch to v8 and incorporated a few changes:

1) Updated documentation, added new tests, and applied minor code
adjustments based on prior review comments.
2) Tweaked the initialization of waitReplayLSNState so that
non-backend processes can call wait for replay.

Started a new thread [1] and attached a patch addressing the polling
issue in the function
read_local_xlog_page_guts built on the infra of patch v8.

[1] https://www.postgresql.org/message-id/[email protected]...

Feedbacks welcome.

Best,
Xuneng


Attachments:

  [application/x-patch] v8-0001-Implement-WAIT-FOR-command.patch (62.9K, ../../CABPTF7WrJ=GOQR2Bh5E8gni8sYiFdmvzEH+nVuuzRFjxsZ-GBg@mail.gmail.com/2-v8-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 4487999a6c393e42619ae77e5e7f14c6cac9f235 Mon Sep 17 00:00:00 2001
From: alterego665 <[email protected]>
Date: Wed, 27 Aug 2025 09:12:38 +0800
Subject: [PATCH v8] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 219 ++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 388 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 284 +++++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  29 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  90 ++++
 src/include/commands/wait.h                   |  21 +
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl       | 269 ++++++++++++
 src/tools/pgindent/typedefs.list              |   5 +-
 30 files changed, 1457 insertions(+), 15 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..ecaff5d5deb 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to the synchronous
+    replication
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..433901baa82
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,219 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed within the specified timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ TIMEOUT <replaceable class="parameter">timeout</replaceable> ] [ NO_THROW ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    as soon as <literal>NO_THROW</literal> is not specified.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Options</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>LSN</literal> '<replaceable class="parameter">lsn</replaceable>'</term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>TIMEOUT</literal> <replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      When specified and <parameter>timeout</parameter> is greater than zero,
+      the command waits until <parameter>lsn</parameter> is reached or
+      the specified <parameter>timeout</parameter> has elapsed.
+     </para>
+     <para>
+      The <parameter>timeout</parameter> might be given as integer number of
+      milliseconds.  Also it might be given as string literal with
+      integer number of milliseconds or a number with unit
+      (see <xref linkend="config-setting-names-values"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NO_THROW</literal></term>
+    <listitem>
+     <para>
+      Specify to not throw an error in the case of timeout or
+      running on the primary.  In this case the result status can be get from
+      the return value.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return values</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this function execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out as soon as <parameter>NO_THROW</parameter> is not specified.
+    However, if <function>pg_wal_replay_wait</function> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+   </programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+   </programlisting>
+  </para>
+
+  <para>
+    It may also happen that target <parameter>lsn</parameter> is not reached
+    within the timeout.  In that case the error is thrown.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT '0.1 s';
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+   </programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT 100 NO_THROW;
+ RESULT STATUS
+---------------
+ timeout
+(1 row)
+   </programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b46e7e9c2a6..7eb4625c5e9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7ffb2179151..f5257dfa689 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6205,6 +6206,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index f23ec8969c2..408454bb8b9 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1837,6 +1838,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..2cc9312e836
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,388 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for the replay of the given LSN on a
+ *		physical standby.  The core idea is very small: every backend that
+ *		wants to wait publishes the LSN it needs to the shared memory, and
+ *		the startup process wakes it once that LSN has been replayed.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by a startup
+ *		process, timeout is reached, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
+ *
+ *		After replaying a WAL record, the startup process first performs a
+ *		fast path check minWaitedLSN > replayLSN.  If this check is negative,
+ *		it checks waitersHeap and wakes up the backend whose awaited LSNs
+ *		are reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitReplayLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const		WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const		WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitReplayLSN->minWaitedLSN according to the current state of
+ * waitReplayLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the pairing heap of waiting processes till we find LSN not
+		 * yet replayed.  Record the process numbers to wake up, but to avoid
+		 * holding the lock for too long, send the wakeups only after
+		 * releasing the lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) &&
+				procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+			procInfo->inHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN();
+
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already replayed.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+		/* Need to recheck if there were more waiters than static array size. */
+	}
+	while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, a timeout happens, the
+ * replica gets promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.  Returns
+ * WAIT_LSN_RESULT_TIMEOUT if the timeout was reached before the target LSN
+ * replayed.  Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..cfa42ad6f6c
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,284 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "nodes/print.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+typedef enum
+{
+	WaitStmtParamNone,
+	WaitStmtParamTimeout,
+	WaitStmtParamLSN
+}			WaitStmtParam;
+
+void
+ExecWaitStmt(WaitStmt * stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	WaitStmtParam curParam = WaitStmtParamNone;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	bool		o_lsn = false;
+	bool		o_timeout = false;
+	bool		o_no_throw = false;
+
+	foreach_ptr(Node, option, stmt->options)
+	{
+		if (IsA(option, String))
+		{
+			String	   *str = castNode(String, option);
+			char	   *name = str_tolower(str->sval, strlen(str->sval),
+										   DEFAULT_COLLATION_OID);
+
+			if (curParam != WaitStmtParamNone)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected parameter after \"%s\"", name)));
+
+			if (strcmp(name, "lsn") == 0)
+			{
+				if (o_lsn)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "lsn")));
+				o_lsn = true;
+				curParam = WaitStmtParamLSN;
+			}
+			else if (strcmp(name, "timeout") == 0)
+			{
+				if (o_timeout)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "timeout")));
+				o_timeout = true;
+				curParam = WaitStmtParamTimeout;
+			}
+			else if (strcmp(name, "no_throw") == 0)
+			{
+				if (o_no_throw)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "no_throw")));
+				o_no_throw = true;
+				throw = false;
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unrecognized parameter \"%s\"", name)));
+
+		}
+		else if (IsA(option, Integer))
+		{
+			Integer    *intVal = castNode(Integer, option);
+
+			if (curParam != WaitStmtParamTimeout)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected integer value")));
+
+			timeout = intVal->ival;
+
+			curParam = WaitStmtParamNone;
+		}
+		else if (IsA(option, A_Const))
+		{
+			A_Const    *constVal = castNode(A_Const, option);
+			String	   *str = &constVal->val.sval;
+
+			if (curParam != WaitStmtParamLSN &&
+				curParam != WaitStmtParamTimeout)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected string value")));
+
+			if (curParam == WaitStmtParamLSN)
+			{
+				lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+													  CStringGetDatum(str->sval)));
+			}
+			else if (curParam == WaitStmtParamTimeout)
+			{
+				const char *hintmsg;
+				double		result;
+
+				if (!parse_real(str->sval, &result, GUC_UNIT_MS, &hintmsg))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for timeout option: \"%s\"",
+									str->sval),
+							 hintmsg ? errhint("%s", _(hintmsg)) : 0));
+				}
+
+				/*
+				 * Get rid of any fractional part in the input. This is so we don't fail
+				 * on just-out-of-range values that would round into range.
+				 */
+				result = rint(result);
+
+				/* Range check */
+				if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+					ereport(ERROR,
+							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+							 errmsg("timeout value is out of range for type bigint")));
+
+				timeout = (int64) result;
+			}
+
+			curParam = WaitStmtParamNone;
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unexpected parameter type")));
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * pg_wal_replay_wait() is a procedure, not a function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt * stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..164fd23017c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -671,6 +671,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <node>	wait_option
+%type <list>	wait_option_list
+
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -785,7 +788,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1113,6 +1116,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16402,6 +16406,25 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR wait_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
+
+wait_option_list:
+			wait_option						{ $$ = list_make1($1); }
+			| wait_option_list wait_option	{ $$ = lappend($1, $2); }
+			;
+
+wait_option: ColLabel						{ $$ = (Node *) makeString($1); }
+			 | NumericOnly					{ $$ = (Node *) $1; }
+			 | Sconst						{ $$ = (Node *) makeStringConst($1, @1); }
+
+		;
 
 /*
  * Aggregate decoration clauses
@@ -18050,6 +18073,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18707,6 +18731,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e9ef0fbfe32..a1cb9f2473e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..880fa7807eb 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 5427da5bc1b..ee20a48b2c5 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -352,6 +353,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..72be2f76293
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,90 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+}			WaitLSNResult;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/* A pairing heap node for participation in waitLSNState->waitersHeap */
+	pairingheap_node phNode;
+}			WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+}			WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState * waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ef9e5f0c0be
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt * stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt * stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b..b8d3fc009fb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4364,4 +4364,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+}			WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..dec7ec6e5ec 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -494,6 +494,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..da1cfeb1c52
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,269 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' TIMEOUT '1d';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' TIMEOUT 10;");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' TIMEOUT 1000;",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' TIMEOUT '0.1 s' NO_THROW;]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' TIMEOUT 10 NO_THROW;]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn = $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql('postgres', "WAIT FOR LSN '${test_lsn}' TIMEOUT -1000;",
+	stderr => \$stderr);
+ok($stderr =~ /timeout.*must not be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter
+$node_standby->psql('postgres', "WAIT FOR LSN '${test_lsn}' UNKNOWN_PARAM;",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized parameter/,
+	"get error for unknown parameter");
+
+# Test duplicate LSN parameter
+$node_standby->psql('postgres', "WAIT FOR LSN '${test_lsn}' LSN '${test_lsn}';",
+	stderr => \$stderr);
+ok($stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate LSN parameter");
+
+# Test duplicate TIMEOUT parameter
+$node_standby->psql('postgres', "WAIT FOR LSN '${test_lsn}' TIMEOUT 1000 TIMEOUT 2000;",
+	stderr => \$stderr);
+ok($stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter
+$node_standby->psql('postgres', "WAIT FOR LSN '${test_lsn}' NO_THROW NO_THROW;",
+	stderr => \$stderr);
+ok($stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;",
+	stderr => \$stderr);
+ok($stderr =~ /lsn.*must be specified/,
+	"get error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql('postgres', "WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql('postgres', "WAIT FOR LSN '${test_lsn}' TIMEOUT 'invalid';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid value for timeout option/,
+	"get error for invalid timeout format");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' TIMEOUT '10ms' NO_THROW;]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..f303f04d007 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -615,7 +615,6 @@ DatumTupleFields
 DbInfo
 DbInfoArr
 DbLocaleInfo
-DbOidName
 DeClonePtrType
 DeadLockState
 DeallocateStmt
@@ -2283,7 +2282,6 @@ PlannerParamItem
 Point
 Pointer
 PolicyInfo
-PolyNumAggState
 Pool
 PopulateArrayContext
 PopulateArrayState
@@ -4129,6 +4127,7 @@ tar_file
 td_entry
 teSection
 temp_tablespaces_extra
+test128
 test_re_flags
 test_regex_ctx
 test_shm_mq_header
@@ -4198,6 +4197,7 @@ varatt_expanded
 varattrib_1b
 varattrib_1b_e
 varattrib_4b
+vartag_external
 vbits
 verifier_context
 walrcv_alter_slot_fn
@@ -4326,7 +4326,6 @@ xmlGenericErrorFunc
 xmlNodePtr
 xmlNodeSetPtr
 xmlParserCtxtPtr
-xmlParserErrors
 xmlParserInputPtr
 xmlSaveCtxt
 xmlSaveCtxtPtr
-- 
2.49.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-09-13 19:31                         ` Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-09-13 19:31 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi, Xuneng!

On Wed, Aug 27, 2025 at 6:54 PM Xuneng Zhou <[email protected]> wrote:
> I did a rebase for the patch to v8 and incorporated a few changes:
>
> 1) Updated documentation, added new tests, and applied minor code
> adjustments based on prior review comments.
> 2) Tweaked the initialization of waitReplayLSNState so that
> non-backend processes can call wait for replay.
>
> Started a new thread [1] and attached a patch addressing the polling
> issue in the function
> read_local_xlog_page_guts built on the infra of patch v8.
>
> [1] https://www.postgresql.org/message-id/[email protected]...
>
> Feedbacks welcome.

Thank you for your reviewing and revising this patch.

I see you've integrated most of your points expressed in [1].  I went
though them and I've integrated the rest of them.  Except this one.

> 11) The synopsis might read more clearly as:
> - WAIT FOR LSN '<lsn>' [ TIMEOUT <milliseconds | 'duration-with-units'> ] [ NO_THROW ]

I didn't find examples on how we do the similar things on other places
of docs.  This is why I decided to leave this place as it currently
is.

Also, I found some mess up with typedefs.list.  I've returned the
changes to typdefs.list back and re-indented the sources.

I'd like to ask your opinion of the way this feature is implemented in
terms of grammar: generic parsing implemented in gram.y and the rest
is done in wait.c.  I think this approach should minimize additional
keywords and states for parsing code.  This comes at the price of more
complex code in wait.c, but I think this is a fair price.

Links.
1. https://www.postgresql.org/message-id/CABPTF7VsoGDMBq34MpLrMSZyxNZvVbgH6-zxtJOg5AwOoYURbw%40mail.gma...

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v9-0001-Implement-WAIT-FOR-command.patch (62.4K, ../../CAPpHfds81GLy-GQ0gk2AgsitRE0T_wRibLTTV9=JAMtNaK8S7Q@mail.gmail.com/2-v9-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 70fff63c02e85a197b727da1657bd24595fc8132 Mon Sep 17 00:00:00 2001
From: alterego665 <[email protected]>
Date: Sun, 24 Aug 2025 20:10:37 +0800
Subject: [PATCH v9] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 218 ++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 388 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 284 +++++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  29 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  93 +++++
 src/include/commands/wait.h                   |  21 +
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl       | 281 +++++++++++++
 src/tools/pgindent/typedefs.list              |   5 +
 30 files changed, 1474 insertions(+), 12 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..328ce7fe8ed
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,218 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ TIMEOUT <replaceable class="parameter">timeout</replaceable> ] [ NO_THROW ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    as soon as <literal>NO_THROW</literal> is not specified.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Options</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>LSN</literal> '<replaceable class="parameter">lsn</replaceable>'</term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>TIMEOUT</literal> <replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      When specified and <parameter>timeout</parameter> is greater than zero,
+      the command waits until <parameter>lsn</parameter> is reached or
+      the specified <parameter>timeout</parameter> has elapsed.
+     </para>
+     <para>
+      The <parameter>timeout</parameter> might be given as integer number of
+      milliseconds.  Also it might be given as string literal with
+      integer number of milliseconds or a number with unit
+      (see <xref linkend="config-setting-names-values"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NO_THROW</literal></term>
+    <listitem>
+     <para>
+      Specify to not throw an error in the case of timeout or
+      running on the primary.  In this case the result status can be get from
+      the return value.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return values</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out as soon as <parameter>NO_THROW</parameter> is not specified.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+   </programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+   </programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT '0.1 s';
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+   </programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT 100 NO_THROW;
+ RESULT STATUS
+---------------
+ timeout
+(1 row)
+   </programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b46e7e9c2a6..7eb4625c5e9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0baf0ac6160..7a078730e28 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6205,6 +6206,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 346319338a0..e709b7392cf 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1837,6 +1838,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..4d831fbfa74
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,388 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for the replay of the given LSN on a
+ *		physical standby.  The core idea is very small: every backend that
+ *		wants to wait publishes the LSN it needs to the shared memory, and
+ *		the startup process wakes it once that LSN has been replayed.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by a startup
+ *		process, timeout is reached, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
+ *
+ *		After replaying a WAL record, the startup process first performs a
+ *		fast path check minWaitedLSN > replayLSN.  If this check is negative,
+ *		it checks waitersHeap and wakes up the backend whose awaited LSNs
+ *		are reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitReplayLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitReplayLSN->minWaitedLSN according to the current state of
+ * waitReplayLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the pairing heap of waiting processes till we find LSN not
+		 * yet replayed.  Record the process numbers to wake up, but to avoid
+		 * holding the lock for too long, send the wakeups only after
+		 * releasing the lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) &&
+				procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+			procInfo->inHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN();
+
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already replayed.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+		/* Need to recheck if there were more waiters than static array size. */
+	}
+	while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, a timeout happens, the
+ * replica gets promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.  Returns
+ * WAIT_LSN_RESULT_TIMEOUT if the timeout was reached before the target LSN
+ * replayed.  Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..1d59ddd81aa
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,284 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+typedef enum
+{
+	WaitStmtParamNone,
+	WaitStmtParamTimeout,
+	WaitStmtParamLSN
+} WaitStmtParam;
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	WaitStmtParam curParam = WaitStmtParamNone;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	bool		haveLsn = false;
+	bool		haveTimeout = false;
+	bool		haveNoThrow = false;
+
+	foreach_ptr(Node, option, stmt->options)
+	{
+		if (IsA(option, String))
+		{
+			String	   *str = castNode(String, option);
+			char	   *name = str_tolower(str->sval, strlen(str->sval),
+										   DEFAULT_COLLATION_OID);
+
+			if (curParam != WaitStmtParamNone)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected parameter after \"%s\"", name)));
+
+			if (strcmp(name, "lsn") == 0)
+			{
+				if (haveLsn)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "lsn")));
+				haveLsn = true;
+				curParam = WaitStmtParamLSN;
+			}
+			else if (strcmp(name, "timeout") == 0)
+			{
+				if (haveTimeout)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "timeout")));
+				haveTimeout = true;
+				curParam = WaitStmtParamTimeout;
+			}
+			else if (strcmp(name, "no_throw") == 0)
+			{
+				if (haveNoThrow)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "no_throw")));
+				haveNoThrow = true;
+				throw = false;
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unrecognized parameter \"%s\"", name)));
+
+		}
+		else if (IsA(option, Integer))
+		{
+			Integer    *intVal = castNode(Integer, option);
+
+			if (curParam != WaitStmtParamTimeout)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected integer value")));
+
+			timeout = intVal->ival;
+
+			curParam = WaitStmtParamNone;
+		}
+		else if (IsA(option, A_Const))
+		{
+			A_Const    *constVal = castNode(A_Const, option);
+			String	   *str = &constVal->val.sval;
+
+			if (curParam != WaitStmtParamLSN &&
+				curParam != WaitStmtParamTimeout)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected string value")));
+
+			if (curParam == WaitStmtParamLSN)
+			{
+				lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+													  CStringGetDatum(str->sval)));
+			}
+			else if (curParam == WaitStmtParamTimeout)
+			{
+				const char *hintmsg;
+				double		result;
+
+				if (!parse_real(str->sval, &result, GUC_UNIT_MS, &hintmsg))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for timeout option: \"%s\"",
+									str->sval),
+							 hintmsg ? errhint("%s", _(hintmsg)) : 0));
+				}
+
+				/*
+				 * Get rid of any fractional part in the input. This is so we
+				 * don't fail on just-out-of-range values that would round
+				 * into range.
+				 */
+				result = rint(result);
+
+				/* Range check */
+				if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+					ereport(ERROR,
+							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+							 errmsg("timeout value is out of range for type bigint")));
+
+				timeout = (int64) result;
+			}
+
+			curParam = WaitStmtParamNone;
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unexpected parameter type")));
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a comment, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..8675dfd2e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -671,6 +671,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <node>	wait_option
+%type <list>	wait_option_list
+
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -785,7 +788,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1113,6 +1116,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16403,6 +16407,25 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR wait_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
+
+wait_option_list:
+			wait_option						{ $$ = list_make1($1); }
+			| wait_option_list wait_option	{ $$ = lappend($1, $2); }
+			;
+
+wait_option: ColLabel						{ $$ = (Node *) makeString($1); }
+			 | NumericOnly					{ $$ = (Node *) $1; }
+			 | Sconst						{ $$ = (Node *) makeStringConst($1, @1); }
+
+		;
 
 /*
  * Aggregate decoration clauses
@@ -18051,6 +18074,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18708,6 +18732,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5f442bc3bd4..398f4d2b363 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2060,6 +2068,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2115,6 +2126,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3092,6 +3106,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3690,6 +3708,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..eb77924c4be 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +356,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..df8202528b9
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,93 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/*
+	 * A pairing heap node for participation in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..b44c37aa4db
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b..fa5fb1a8897 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4364,4 +4364,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..dec7ec6e5ec 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -494,6 +494,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..9d06b5c060f
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,281 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' TIMEOUT '1d';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' TIMEOUT 10;");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' TIMEOUT 1000;",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' TIMEOUT '0.1 s' NO_THROW;]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' TIMEOUT 10 NO_THROW;]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' TIMEOUT -1000;",
+	stderr => \$stderr);
+ok($stderr =~ /timeout.*must not be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' UNKNOWN_PARAM;",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized parameter/, "get error for unknown parameter");
+
+# Test duplicate LSN parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' LSN '${test_lsn}';",
+	stderr => \$stderr);
+ok( $stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate LSN parameter");
+
+# Test duplicate TIMEOUT parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' TIMEOUT 1000 TIMEOUT 2000;",
+	stderr => \$stderr);
+ok( $stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' NO_THROW NO_THROW;",
+	stderr => \$stderr);
+ok( $stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /lsn.*must be specified/, "get error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' TIMEOUT 'invalid';",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid value for timeout option/,
+	"get error for invalid timeout format");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' TIMEOUT '10ms' NO_THROW;]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..49dab055752 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3255,7 +3255,12 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
+WaitStmtParam
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-09-14 13:51                           ` Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-09-14 13:51 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi Alexander,

On Sun, Sep 14, 2025 at 3:31 AM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> On Wed, Aug 27, 2025 at 6:54 PM Xuneng Zhou <[email protected]> wrote:
> > I did a rebase for the patch to v8 and incorporated a few changes:
> >
> > 1) Updated documentation, added new tests, and applied minor code
> > adjustments based on prior review comments.
> > 2) Tweaked the initialization of waitReplayLSNState so that
> > non-backend processes can call wait for replay.
> >
> > Started a new thread [1] and attached a patch addressing the polling
> > issue in the function
> > read_local_xlog_page_guts built on the infra of patch v8.
> >
> > [1] https://www.postgresql.org/message-id/[email protected]...
> >
> > Feedbacks welcome.
>
> Thank you for your reviewing and revising this patch.
>
> I see you've integrated most of your points expressed in [1].  I went
> though them and I've integrated the rest of them.  Except this one.
>
> > 11) The synopsis might read more clearly as:
> > - WAIT FOR LSN '<lsn>' [ TIMEOUT <milliseconds | 'duration-with-units'> ] [ NO_THROW ]
>
> I didn't find examples on how we do the similar things on other places
> of docs.  This is why I decided to leave this place as it currently
> is.

+1. I re-check other commands with similar parameter patterns, and
they follow the approach in v9.

>
> Also, I found some mess up with typedefs.list.  I've returned the
> changes to typdefs.list back and re-indented the sources.

 Thanks for catching and fixing that.

> I'd like to ask your opinion of the way this feature is implemented in
> terms of grammar: generic parsing implemented in gram.y and the rest
> is done in wait.c.  I think this approach should minimize additional
> keywords and states for parsing code.  This comes at the price of more
> complex code in wait.c, but I think this is a fair price.

It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
PUBLICATION - all use minimal grammar rules that produce generic
option lists, with the actual interpretation done in their respective
implementation files. The moderate complexity in wait.c seems
acceptable.

Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-09-15 18:59                             ` Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-09-15 18:59 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi, Xuneng!

On Sun, Sep 14, 2025 at 4:51 PM Xuneng Zhou <[email protected]> wrote:
>
> On Sun, Sep 14, 2025 at 3:31 AM Alexander Korotkov <[email protected]> wrote:
> > On Wed, Aug 27, 2025 at 6:54 PM Xuneng Zhou <[email protected]> wrote:
> > > I did a rebase for the patch to v8 and incorporated a few changes:
> > >
> > > 1) Updated documentation, added new tests, and applied minor code
> > > adjustments based on prior review comments.
> > > 2) Tweaked the initialization of waitReplayLSNState so that
> > > non-backend processes can call wait for replay.
> > >
> > > Started a new thread [1] and attached a patch addressing the polling
> > > issue in the function
> > > read_local_xlog_page_guts built on the infra of patch v8.
> > >
> > > [1] https://www.postgresql.org/message-id/[email protected]...
> > >
> > > Feedbacks welcome.
> >
> > Thank you for your reviewing and revising this patch.
> >
> > I see you've integrated most of your points expressed in [1].  I went
> > though them and I've integrated the rest of them.  Except this one.
> >
> > > 11) The synopsis might read more clearly as:
> > > - WAIT FOR LSN '<lsn>' [ TIMEOUT <milliseconds | 'duration-with-units'> ] [ NO_THROW ]
> >
> > I didn't find examples on how we do the similar things on other places
> > of docs.  This is why I decided to leave this place as it currently
> > is.
>
> +1. I re-check other commands with similar parameter patterns, and
> they follow the approach in v9.
>
> >
> > Also, I found some mess up with typedefs.list.  I've returned the
> > changes to typdefs.list back and re-indented the sources.
>
>  Thanks for catching and fixing that.
>
> > I'd like to ask your opinion of the way this feature is implemented in
> > terms of grammar: generic parsing implemented in gram.y and the rest
> > is done in wait.c.  I think this approach should minimize additional
> > keywords and states for parsing code.  This comes at the price of more
> > complex code in wait.c, but I think this is a fair price.
>
> It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> PUBLICATION - all use minimal grammar rules that produce generic
> option lists, with the actual interpretation done in their respective
> implementation files. The moderate complexity in wait.c seems
> acceptable.

The attached revision of patch contains fix of the typo in the comment
you reported off-list.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v10-0001-Implement-WAIT-FOR-command.patch (62.4K, ../../CAPpHfdsz0tfBJuY1QBsGtO2AXo-JDvTV=rc7sjWru1+2CjfAQA@mail.gmail.com/2-v10-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 63c1d54b6a2933167271277dc6ed3c3af70dd703 Mon Sep 17 00:00:00 2001
From: alterego665 <[email protected]>
Date: Sun, 24 Aug 2025 20:10:37 +0800
Subject: [PATCH v10] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 218 ++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 388 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 284 +++++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  29 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  93 +++++
 src/include/commands/wait.h                   |  21 +
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl       | 281 +++++++++++++
 src/tools/pgindent/typedefs.list              |   5 +
 30 files changed, 1474 insertions(+), 12 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..328ce7fe8ed
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,218 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ TIMEOUT <replaceable class="parameter">timeout</replaceable> ] [ NO_THROW ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    as soon as <literal>NO_THROW</literal> is not specified.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Options</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>LSN</literal> '<replaceable class="parameter">lsn</replaceable>'</term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>TIMEOUT</literal> <replaceable class="parameter">timeout</replaceable></term>
+    <listitem>
+     <para>
+      When specified and <parameter>timeout</parameter> is greater than zero,
+      the command waits until <parameter>lsn</parameter> is reached or
+      the specified <parameter>timeout</parameter> has elapsed.
+     </para>
+     <para>
+      The <parameter>timeout</parameter> might be given as integer number of
+      milliseconds.  Also it might be given as string literal with
+      integer number of milliseconds or a number with unit
+      (see <xref linkend="config-setting-names-values"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NO_THROW</literal></term>
+    <listitem>
+     <para>
+      Specify to not throw an error in the case of timeout or
+      running on the primary.  In this case the result status can be get from
+      the return value.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return values</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out as soon as <parameter>NO_THROW</parameter> is not specified.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+   </programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+   </programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT '0.1 s';
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+   </programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+
+   <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' TIMEOUT 100 NO_THROW;
+ RESULT STATUS
+---------------
+ timeout
+(1 row)
+   </programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b46e7e9c2a6..7eb4625c5e9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0baf0ac6160..7a078730e28 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6205,6 +6206,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 346319338a0..e709b7392cf 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1837,6 +1838,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..4d831fbfa74
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,388 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for the replay of the given LSN on a
+ *		physical standby.  The core idea is very small: every backend that
+ *		wants to wait publishes the LSN it needs to the shared memory, and
+ *		the startup process wakes it once that LSN has been replayed.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by a startup
+ *		process, timeout is reached, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
+ *
+ *		After replaying a WAL record, the startup process first performs a
+ *		fast path check minWaitedLSN > replayLSN.  If this check is negative,
+ *		it checks waitersHeap and wakes up the backend whose awaited LSNs
+ *		are reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitReplayLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitReplayLSN->minWaitedLSN according to the current state of
+ * waitReplayLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the pairing heap of waiting processes till we find LSN not
+		 * yet replayed.  Record the process numbers to wake up, but to avoid
+		 * holding the lock for too long, send the wakeups only after
+		 * releasing the lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) &&
+				procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+			procInfo->inHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN();
+
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already replayed.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+		/* Need to recheck if there were more waiters than static array size. */
+	}
+	while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, a timeout happens, the
+ * replica gets promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.  Returns
+ * WAIT_LSN_RESULT_TIMEOUT if the timeout was reached before the target LSN
+ * replayed.  Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..ffcc0bbf457
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,284 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "catalog/pg_collation_d.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/fmgrprotos.h"
+#include "utils/formatting.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+typedef enum
+{
+	WaitStmtParamNone,
+	WaitStmtParamTimeout,
+	WaitStmtParamLSN
+} WaitStmtParam;
+
+void
+ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn = InvalidXLogRecPtr;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	WaitStmtParam curParam = WaitStmtParamNone;
+
+	/*
+	 * Process the list of parameters.
+	 */
+	bool		haveLsn = false;
+	bool		haveTimeout = false;
+	bool		haveNoThrow = false;
+
+	foreach_ptr(Node, option, stmt->options)
+	{
+		if (IsA(option, String))
+		{
+			String	   *str = castNode(String, option);
+			char	   *name = str_tolower(str->sval, strlen(str->sval),
+										   DEFAULT_COLLATION_OID);
+
+			if (curParam != WaitStmtParamNone)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected parameter after \"%s\"", name)));
+
+			if (strcmp(name, "lsn") == 0)
+			{
+				if (haveLsn)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "lsn")));
+				haveLsn = true;
+				curParam = WaitStmtParamLSN;
+			}
+			else if (strcmp(name, "timeout") == 0)
+			{
+				if (haveTimeout)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "timeout")));
+				haveTimeout = true;
+				curParam = WaitStmtParamTimeout;
+			}
+			else if (strcmp(name, "no_throw") == 0)
+			{
+				if (haveNoThrow)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("parameter \"%s\" specified more than once", "no_throw")));
+				haveNoThrow = true;
+				throw = false;
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unrecognized parameter \"%s\"", name)));
+
+		}
+		else if (IsA(option, Integer))
+		{
+			Integer    *intVal = castNode(Integer, option);
+
+			if (curParam != WaitStmtParamTimeout)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected integer value")));
+
+			timeout = intVal->ival;
+
+			curParam = WaitStmtParamNone;
+		}
+		else if (IsA(option, A_Const))
+		{
+			A_Const    *constVal = castNode(A_Const, option);
+			String	   *str = &constVal->val.sval;
+
+			if (curParam != WaitStmtParamLSN &&
+				curParam != WaitStmtParamTimeout)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unexpected string value")));
+
+			if (curParam == WaitStmtParamLSN)
+			{
+				lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+													  CStringGetDatum(str->sval)));
+			}
+			else if (curParam == WaitStmtParamTimeout)
+			{
+				const char *hintmsg;
+				double		result;
+
+				if (!parse_real(str->sval, &result, GUC_UNIT_MS, &hintmsg))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for timeout option: \"%s\"",
+									str->sval),
+							 hintmsg ? errhint("%s", _(hintmsg)) : 0));
+				}
+
+				/*
+				 * Get rid of any fractional part in the input. This is so we
+				 * don't fail on just-out-of-range values that would round
+				 * into range.
+				 */
+				result = rint(result);
+
+				/* Range check */
+				if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+					ereport(ERROR,
+							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+							 errmsg("timeout value is out of range for type bigint")));
+
+				timeout = (int64) result;
+			}
+
+			curParam = WaitStmtParamNone;
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unexpected parameter type")));
+	}
+
+	if (XLogRecPtrIsInvalid(lsn))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("\"lsn\" must be specified")));
+
+	if (timeout < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("\"timeout\" must not be negative")));
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				 errdetail("Make sure WAIT FOR isn't called within a transaction with an isolation level higher than READ COMMITTED, procedure, or a function.")));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("timed out while waiting for target LSN %X/%X to be replayed; current replay LSN %X/%X",
+								LSN_FORMAT_ARGS(lsn),
+								LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errdetail("Recovery ended before replaying target LSN %X/%X; last replay LSN %X/%X.",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))));
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("recovery is not in progress"),
+							 errhint("Waiting for the replay LSN can only be executed during recovery.")));
+				}
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..8675dfd2e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -671,6 +671,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <node>	wait_option
+%type <list>	wait_option_list
+
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -785,7 +788,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1113,6 +1116,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16403,6 +16407,25 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+WaitStmt:
+			WAIT FOR wait_option_list
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->options = $3;
+					$$ = (Node *)n;
+				}
+			;
+
+wait_option_list:
+			wait_option						{ $$ = list_make1($1); }
+			| wait_option_list wait_option	{ $$ = lappend($1, $2); }
+			;
+
+wait_option: ColLabel						{ $$ = (Node *) makeString($1); }
+			 | NumericOnly					{ $$ = (Node *) $1; }
+			 | Sconst						{ $$ = (Node *) makeStringConst($1, @1); }
+
+		;
 
 /*
  * Aggregate decoration clauses
@@ -18051,6 +18074,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18708,6 +18732,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5f442bc3bd4..398f4d2b363 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt((WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2060,6 +2068,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2115,6 +2126,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3092,6 +3106,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3690,6 +3708,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..eb77924c4be 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +356,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..df8202528b9
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,93 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/*
+	 * A pairing heap node for participation in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..b44c37aa4db
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b..fa5fb1a8897 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4364,4 +4364,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	List	   *options;
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..dec7ec6e5ec 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -494,6 +494,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..9d06b5c060f
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,281 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' TIMEOUT '1d';
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' TIMEOUT 10;");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' TIMEOUT 1000;",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' TIMEOUT '0.1 s' NO_THROW;]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' TIMEOUT 10 NO_THROW;]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' TIMEOUT -1000;",
+	stderr => \$stderr);
+ok($stderr =~ /timeout.*must not be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' UNKNOWN_PARAM;",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized parameter/, "get error for unknown parameter");
+
+# Test duplicate LSN parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' LSN '${test_lsn}';",
+	stderr => \$stderr);
+ok( $stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate LSN parameter");
+
+# Test duplicate TIMEOUT parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' TIMEOUT 1000 TIMEOUT 2000;",
+	stderr => \$stderr);
+ok( $stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' NO_THROW NO_THROW;",
+	stderr => \$stderr);
+ok( $stderr =~ /parameter.*specified more than once/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /lsn.*must be specified/, "get error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' TIMEOUT 'invalid';",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid value for timeout option/,
+	"get error for invalid timeout format");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR lsn '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' TIMEOUT '10ms' NO_THROW;]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..49dab055752 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3255,7 +3255,12 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
+WaitStmtParam
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-09-15 20:24                               ` Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Álvaro Herrera @ 2025-09-15 20:24 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

On 2025-Sep-15, Alexander Korotkov wrote:

> > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > PUBLICATION - all use minimal grammar rules that produce generic
> > option lists, with the actual interpretation done in their respective
> > implementation files. The moderate complexity in wait.c seems
> > acceptable.

Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
lists of DefElem (a name optionally followed by a value) instead of
individual scattered elements that must later be matched up.  Why not
use utility_option_list instead and then loop on the list of DefElems?
It'd be a lot simpler.

Also, we've found that failing to surround the options by parens leads
to pain down the road, so maybe add that.  Given that the LSN seems to
be mandatory, maybe make it something like

WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]

This requires that you make LSN a keyword, albeit unreserved.  Or you
could make it
WAIT FOR Ident [the rest]
and then ensure in C that the identifier matches the word LSN, such as
we do for "permissive" and "restrictive" in
RowSecurityDefaultPermissive.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2025-09-26 11:22                                 ` Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-09-26 11:22 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi Álvaro,

Thanks for your review.

On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
>
> On 2025-Sep-15, Alexander Korotkov wrote:
>
> > > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > > PUBLICATION - all use minimal grammar rules that produce generic
> > > option lists, with the actual interpretation done in their respective
> > > implementation files. The moderate complexity in wait.c seems
> > > acceptable.
>
> Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> lists of DefElem (a name optionally followed by a value) instead of
> individual scattered elements that must later be matched up.  Why not
> use utility_option_list instead and then loop on the list of DefElems?
> It'd be a lot simpler.

I took a look at commands like VACUUM and EXPLAIN and they do follow
this pattern. v11 will make use of utility_option_list.

> Also, we've found that failing to surround the options by parens leads
> to pain down the road, so maybe add that.  Given that the LSN seems to
> be mandatory, maybe make it something like
>
> WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
>
> This requires that you make LSN a keyword, albeit unreserved.  Or you
> could make it
> WAIT FOR Ident [the rest]
> and then ensure in C that the identifier matches the word LSN, such as
> we do for "permissive" and "restrictive" in
> RowSecurityDefaultPermissive.

Shall make LSN an unreserved keyword as well.

Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-09-28 09:02                                   ` Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-09-28 09:02 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; +Cc: jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi Álvaro,
>
> Thanks for your review.
>
> On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
> >
> > On 2025-Sep-15, Alexander Korotkov wrote:
> >
> > > > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > > > PUBLICATION - all use minimal grammar rules that produce generic
> > > > option lists, with the actual interpretation done in their respective
> > > > implementation files. The moderate complexity in wait.c seems
> > > > acceptable.
> >
> > Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> > lists of DefElem (a name optionally followed by a value) instead of
> > individual scattered elements that must later be matched up.  Why not
> > use utility_option_list instead and then loop on the list of DefElems?
> > It'd be a lot simpler.
>
> I took a look at commands like VACUUM and EXPLAIN and they do follow
> this pattern. v11 will make use of utility_option_list.
>
> > Also, we've found that failing to surround the options by parens leads
> > to pain down the road, so maybe add that.  Given that the LSN seems to
> > be mandatory, maybe make it something like
> >
> > WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
> >
> > This requires that you make LSN a keyword, albeit unreserved.  Or you
> > could make it
> > WAIT FOR Ident [the rest]
> > and then ensure in C that the identifier matches the word LSN, such as
> > we do for "permissive" and "restrictive" in
> > RowSecurityDefaultPermissive.
>
> Shall make LSN an unreserved keyword as well.

Here's the updated v11.  Many thanks Jian for off-list discussions and review.

Best,
Xuneng


Attachments:

  [application/octet-stream] v11-0001-Implement-WAIT-FOR-command.patch (63.5K, ../../CABPTF7UAVkq=5qibgbub096YYFDjqZeerryZt8AdP4+wsTrCrQ@mail.gmail.com/2-v11-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 0ee9a9275cd811f70a49560e0715556820fb81be Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sat, 27 Sep 2025 23:26:22 +0800
Subject: [PATCH v11] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 234 +++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 388 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 212 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  33 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  93 +++++
 src/include/commands/wait.h                   |  22 +
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   8 +
 src/include/parser/kwlist.h                   |   2 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl       | 293 +++++++++++++
 src/tools/pgindent/typedefs.list              |   5 +
 30 files changed, 1435 insertions(+), 14 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..8df1f2ab953
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ [WITH] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 109713315c0..36b8ac6b855 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6222,6 +6223,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..824b0942b34 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..4d831fbfa74
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,388 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for the replay of the given LSN on a
+ *		physical standby.  The core idea is very small: every backend that
+ *		wants to wait publishes the LSN it needs to the shared memory, and
+ *		the startup process wakes it once that LSN has been replayed.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by a startup
+ *		process, timeout is reached, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
+ *
+ *		After replaying a WAL record, the startup process first performs a
+ *		fast path check minWaitedLSN > replayLSN.  If this check is negative,
+ *		it checks waitersHeap and wakes up the backend whose awaited LSNs
+ *		are reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitReplayLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitReplayLSN->minWaitedLSN according to the current state of
+ * waitReplayLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the pairing heap of waiting processes till we find LSN not
+		 * yet replayed.  Record the process numbers to wake up, but to avoid
+		 * holding the lock for too long, send the wakeups only after
+		 * releasing the lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) &&
+				procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+			procInfo->inHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN();
+
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already replayed.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+		/* Need to recheck if there were more waiters than static array size. */
+	}
+	while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, a timeout happens, the
+ * replica gets promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.  Returns
+ * WAIT_LSN_RESULT_TIMEOUT if the timeout was reached before the target LSN
+ * replayed.  Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..fd95f24fa74 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -319,6 +319,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -671,7 +672,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -741,7 +741,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -785,7 +785,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1113,6 +1113,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16403,6 +16404,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			opt_with '(' utility_option_list ')'	{ $$ = $3; }
+			| /*EMPTY*/							    { $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17882,6 +17903,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18051,6 +18073,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18497,6 +18520,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18708,6 +18732,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..eb77924c4be 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +356,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..df8202528b9
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,93 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/*
+	 * A pairing heap node for participation in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f1706df58fd..997c72ab858 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4363,4 +4363,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..69a81e21fbb 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -269,6 +269,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -494,6 +495,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..62fdc7cd06c
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,293 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d5a80b4359f..ac0252936be 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3257,7 +3257,12 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
+WaitStmt
+WaitStmtParam
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-04 01:35                                     ` Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 05:48                                       ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-04 01:35 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; +Cc: jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

On Sun, Sep 28, 2025 at 5:02 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi Álvaro,
> >
> > Thanks for your review.
> >
> > On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
> > >
> > > On 2025-Sep-15, Alexander Korotkov wrote:
> > >
> > > > > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > > > > PUBLICATION - all use minimal grammar rules that produce generic
> > > > > option lists, with the actual interpretation done in their respective
> > > > > implementation files. The moderate complexity in wait.c seems
> > > > > acceptable.
> > >
> > > Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> > > lists of DefElem (a name optionally followed by a value) instead of
> > > individual scattered elements that must later be matched up.  Why not
> > > use utility_option_list instead and then loop on the list of DefElems?
> > > It'd be a lot simpler.
> >
> > I took a look at commands like VACUUM and EXPLAIN and they do follow
> > this pattern. v11 will make use of utility_option_list.
> >
> > > Also, we've found that failing to surround the options by parens leads
> > > to pain down the road, so maybe add that.  Given that the LSN seems to
> > > be mandatory, maybe make it something like
> > >
> > > WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
> > >
> > > This requires that you make LSN a keyword, albeit unreserved.  Or you
> > > could make it
> > > WAIT FOR Ident [the rest]
> > > and then ensure in C that the identifier matches the word LSN, such as
> > > we do for "permissive" and "restrictive" in
> > > RowSecurityDefaultPermissive.
> >
> > Shall make LSN an unreserved keyword as well.
>
> Here's the updated v11.  Many thanks Jian for off-list discussions and review.

v12 removed unused
+WaitStmt
+WaitStmtParam in pgindent/typedefs.list.

Best,
Xuneng


Attachments:

  [application/octet-stream] v12-0001-Implement-WAIT-FOR-command.patch (63.4K, ../../CABPTF7XfUUvwrNGH=wXr9zEkdA5+QEVO4fju+3yQ9PoMr0fitQ@mail.gmail.com/2-v12-0001-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From d6fbbb3b0ad81c18657e6fafa50852bc9bf239e2 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sat, 27 Sep 2025 23:26:22 +0800
Subject: [PATCH v12] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

The queue of waiters is stored in the shared memory as an LSN-ordered pairing
heap, where the waiter with the nearest LSN stays on the top.  During
the replay of WAL, waiters whose LSNs have already been replayed are deleted
from the shared memory pairing heap and woken up by setting their latches.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.
---
 doc/src/sgml/high-availability.sgml           |  54 +++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/wait_for.sgml                | 234 +++++++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xact.c             |   6 +
 src/backend/access/transam/xlog.c             |   7 +
 src/backend/access/transam/xlogrecovery.c     |  11 +
 src/backend/access/transam/xlogwait.c         | 388 ++++++++++++++++++
 src/backend/commands/Makefile                 |   3 +-
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/wait.c                   | 212 ++++++++++
 src/backend/lib/pairingheap.c                 |  18 +-
 src/backend/parser/gram.y                     |  33 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/lmgr/proc.c               |   6 +
 src/backend/tcop/pquery.c                     |  12 +-
 src/backend/tcop/utility.c                    |  22 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/include/access/xlogwait.h                 |  93 +++++
 src/include/commands/wait.h                   |  22 +
 src/include/lib/pairingheap.h                 |   3 +
 src/include/nodes/parsenodes.h                |   8 +
 src/include/parser/kwlist.h                   |   2 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/test/recovery/meson.build                 |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl       | 293 +++++++++++++
 src/tools/pgindent/typedefs.list              |   3 +
 30 files changed, 1433 insertions(+), 14 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/access/xlogwait.h
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..8df1f2ab953
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ [WITH] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 109713315c0..36b8ac6b855 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6222,6 +6223,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..824b0942b34 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN)))
+				WaitLSNWakeup(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..4d831fbfa74
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,388 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for the given replay LSN, which is used in
+ *	  WAIT FOR lsn '...'
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for the replay of the given LSN on a
+ *		physical standby.  The core idea is very small: every backend that
+ *		wants to wait publishes the LSN it needs to the shared memory, and
+ *		the startup process wakes it once that LSN has been replayed.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by a startup
+ *		process, timeout is reached, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
+ *
+ *		After replaying a WAL record, the startup process first performs a
+ *		fast path check minWaitedLSN > replayLSN.  If this check is negative,
+ *		it checks waitersHeap and wakes up the backend whose awaited LSNs
+ *		are reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap, waitlsn_cmp, NULL);
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for waitReplayLSN->waitersHeap heap.  Waiting processes are
+ * ordered by lsn, so that the waiter with smallest lsn is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, phNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, phNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update waitReplayLSN->minWaitedLSN according to the current state of
+ * waitReplayLSN->waitersHeap.
+ */
+static void
+updateMinWaitedLSN(void)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+
+		minWaitedLSN = pairingheap_container(WaitLSNProcInfo, phNode, node)->waitLSN;
+	}
+
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN, minWaitedLSN);
+}
+
+/*
+ * Put the current process into the heap of LSN waiters.
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	Assert(!procInfo->inHeap);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	pairingheap_add(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = true;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove the current process from the heap of LSN waiters if it's there.
+ */
+static void
+deleteLSNWaiter(void)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (!procInfo->inHeap)
+	{
+		LWLockRelease(WaitLSNLock);
+		return;
+	}
+
+	pairingheap_remove(&waitLSNState->waitersHeap, &procInfo->phNode);
+	procInfo->inHeap = false;
+	updateMinWaitedLSN();
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been replayed from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+void
+WaitLSNWakeup(XLogRecPtr currentLSN)
+{
+	int			i;
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the pairing heap of waiting processes till we find LSN not
+		 * yet replayed.  Record the process numbers to wake up, but to avoid
+		 * holding the lock for too long, send the wakeups only after
+		 * releasing the lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, phNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) &&
+				procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap);
+			procInfo->inHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN();
+
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already replayed.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+		/* Need to recheck if there were more waiters than static array size. */
+	}
+	while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Delete our item from shmem array if any.
+ */
+void
+WaitLSNCleanup(void)
+{
+	/*
+	 * We do a fast-path check of the 'inHeap' flag without the lock.  This
+	 * flag is set to true only by the process itself.  So, it's only possible
+	 * to get a false positive.  But that will be eliminated by a recheck
+	 * inside deleteLSNWaiter().
+	 */
+	if (waitLSNState->procInfos[MyProcNumber].inHeap)
+		deleteLSNWaiter();
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, a timeout happens, the
+ * replica gets promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.  Returns
+ * WAIT_LSN_RESULT_TIMEOUT if the timeout was reached before the target LSN
+ * replayed.  Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (!RecoveryInProgress())
+	{
+		/*
+		 * Recovery is not in progress.  Given that we detected this in the
+		 * very first check, this procedure was mistakenly called on primary.
+		 * However, it's possible that standby was promoted concurrently to
+		 * the procedure call, while target LSN is replayed.  So, we still
+		 * check the last replay LSN before reporting an error.
+		 */
+		if (PromoteIsTriggered() && targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+		return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+	}
+	else
+	{
+		/* If target LSN is already replayed, exit immediately */
+		if (targetLSN <= GetXLogReplayRecPtr(NULL))
+			return WAIT_LSN_RESULT_SUCCESS;
+	}
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the pairing heap of waiters.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter();
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory pairing heap.  We might
+	 * already be deleted by the startup process.  The 'inHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter();
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..fd95f24fa74 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -319,6 +319,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -671,7 +672,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -741,7 +741,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -785,7 +785,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1113,6 +1113,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16403,6 +16404,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			opt_with '(' utility_option_list ')'	{ $$ = $3; }
+			| /*EMPTY*/							    { $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17882,6 +17903,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18051,6 +18073,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18497,6 +18520,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18708,6 +18732,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..eb77924c4be 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +356,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..df8202528b9
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,93 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN replay.  An item of
+ * waitLSN->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is replayed */
+	ProcNumber	procno;
+
+	/*
+	 * A flag indicating that this item is present in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	bool		inHeap;
+
+	/*
+	 * A pairing heap node for participation in
+	 * waitReplayLSNState->waitersHeap
+	 */
+	pairingheap_node phNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the replay LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN;
+
+	/*
+	 * A pairing heap of waiting processes order by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f1706df58fd..997c72ab858 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4363,4 +4363,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..69a81e21fbb 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -269,6 +269,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -494,6 +495,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..62fdc7cd06c
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,293 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d5a80b4359f..e6ff42b9ea0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3257,6 +3257,9 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-14 13:03                                       ` Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-14 13:03 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; +Cc: jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

On Sat, Oct 4, 2025 at 9:35 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Sun, Sep 28, 2025 at 5:02 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi Álvaro,
> > >
> > > Thanks for your review.
> > >
> > > On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
> > > >
> > > > On 2025-Sep-15, Alexander Korotkov wrote:
> > > >
> > > > > > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > > > > > PUBLICATION - all use minimal grammar rules that produce generic
> > > > > > option lists, with the actual interpretation done in their respective
> > > > > > implementation files. The moderate complexity in wait.c seems
> > > > > > acceptable.
> > > >
> > > > Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> > > > lists of DefElem (a name optionally followed by a value) instead of
> > > > individual scattered elements that must later be matched up.  Why not
> > > > use utility_option_list instead and then loop on the list of DefElems?
> > > > It'd be a lot simpler.
> > >
> > > I took a look at commands like VACUUM and EXPLAIN and they do follow
> > > this pattern. v11 will make use of utility_option_list.
> > >
> > > > Also, we've found that failing to surround the options by parens leads
> > > > to pain down the road, so maybe add that.  Given that the LSN seems to
> > > > be mandatory, maybe make it something like
> > > >
> > > > WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
> > > >
> > > > This requires that you make LSN a keyword, albeit unreserved.  Or you
> > > > could make it
> > > > WAIT FOR Ident [the rest]
> > > > and then ensure in C that the identifier matches the word LSN, such as
> > > > we do for "permissive" and "restrictive" in
> > > > RowSecurityDefaultPermissive.
> > >
> > > Shall make LSN an unreserved keyword as well.
> >
> > Here's the updated v11.  Many thanks Jian for off-list discussions and review.
>
> v12 removed unused
> +WaitStmt
> +WaitStmtParam in pgindent/typedefs.list.
>

Hi, I’ve split the patch into multiple patch sets for easier review,
per Michael’s advice [1].

[1] https://www.postgresql.org/message-id/aOMsv9TszlB1n-W7%40paquier.xyz

Best,
Xuneng


Attachments:

  [application/x-patch] v13-0003-Implement-WAIT-FOR-command.patch (46.6K, ../../CABPTF7UUL5SrOddBWG0Ud65YgAOz8aSzfjEjFd_NF94JzfWgxQ@mail.gmail.com/2-v13-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From c3dd9972d8043c07247bb3e2b476026268ee1bad Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 14 Oct 2025 20:50:04 +0800
Subject: [PATCH v13 3/3] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Co-authored-by: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/access/transam/xlogwait.c     |  27 +-
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 ++++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   5 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/access/xlogwait.h             |   3 +-
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 293 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   3 +
 23 files changed, 951 insertions(+), 13 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..8df1f2ab953
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ [WITH] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..b5e07a724f5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6225,6 +6226,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeupReplay(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..1859d2084e8 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN)))
+				WaitLSNWakeupReplay(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index a114738bddf..7c8134f1209 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -373,9 +373,10 @@ WaitLSNCleanup(void)
  * or replica got promoted before the target LSN replayed.
  */
 WaitLSNResult
-WaitForLSNReplay(XLogRecPtr targetLSN)
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
 {
 	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
 	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
 
 	/* Shouldn't be called when shmem isn't initialized */
@@ -404,6 +405,12 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 			return WAIT_LSN_RESULT_SUCCESS;
 	}
 
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
 	/*
 	 * Add our process to the replay waiters heap.  It might happen that
 	 * target LSN gets replayed before we do.  Another check at the beginning
@@ -438,6 +445,18 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 				break;
 		}
 
+		/*
+		 * If the timeout value is specified, calculate the number of
+		 * milliseconds before the timeout.  Exit if the timeout is already
+		 * reached.
+		 */
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
@@ -464,6 +483,12 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	 */
 	deleteLSNWaiter(WAIT_LSN_REPLAY);
 
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
 	return WAIT_LSN_RESULT_SUCCESS;
 }
 
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 21caf2d43bf..1d016df1f6b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16453,6 +16454,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			opt_with '(' utility_option_list ')'	{ $$ = $3; }
+			| /*EMPTY*/							    { $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17940,6 +17961,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18110,6 +18132,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18556,6 +18579,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18767,6 +18791,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..f8685fa9039 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -947,6 +947,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 441bf475b4d..2e33a1d22d0 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -27,6 +27,7 @@ typedef enum
 	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
 	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
 										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
 } WaitLSNResult;
 
 /*
@@ -106,7 +107,7 @@ extern void WaitLSNShmemInit(void);
 extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
 extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
-extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
 extern void WaitForLSNFlush(XLogRecPtr targetLSN);
 
 #endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc09d1a3f03..c741099e186 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4384,4 +4384,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..62fdc7cd06c
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,293 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..a2c93c0ef4e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3263,6 +3263,9 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



  [application/x-patch] v13-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (24.8K, ../../CABPTF7UUL5SrOddBWG0Ud65YgAOz8aSzfjEjFd_NF94JzfWgxQ@mail.gmail.com/3-v13-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 32dab7ed64eecb62adce6b1d124b1fa389515e74 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Oct 2025 16:35:38 +0800
Subject: [PATCH v13 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 525 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 | 112 ++++
 src/include/storage/lwlocklist.h              |   1 +
 7 files changed, 647 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..4faed65765c
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,525 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *	  Used by internal WAL reading operations.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+static int	waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		/* Initialize replay heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedReplayLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->replayWaitersHeap, waitlsn_replay_cmp, (void *)(uintptr_t)WAIT_LSN_REPLAY);
+
+		/* Initialize flush heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedFlushLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->flushWaitersHeap, waitlsn_flush_cmp, (void *)(uintptr_t)WAIT_LSN_FLUSH);
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for replay waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Comparison function for flush waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNOperation operation)
+{
+	XLogRecPtr minWaitedLSN = PG_UINT64_MAX;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		if (!pairingheap_is_empty(&waitLSNState->replayWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->replayWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedReplayLSN, minWaitedLSN);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		if (!pairingheap_is_empty(&waitLSNState->flushWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->flushWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedFlushLSN, minWaitedLSN);
+	}
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		Assert(!procInfo->inReplayHeap);
+		pairingheap_add(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		Assert(!procInfo->inFlushHeap);
+		pairingheap_add(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (operation == WAIT_LSN_REPLAY && procInfo->inReplayHeap)
+	{
+		pairingheap_remove(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else if (operation == WAIT_LSN_FLUSH && procInfo->inFlushHeap)
+	{
+		pairingheap_remove(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNOperation operation, XLogRecPtr currentLSN)
+{
+	ProcNumber wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int numWakeUpProcs;
+	int i;
+	pairingheap *heap;
+
+	/* Select appropriate heap */
+	heap = (operation == WAIT_LSN_REPLAY) ?
+		   &waitLSNState->replayWaitersHeap :
+		   &waitLSNState->flushWaitersHeap;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached.
+		 * Record process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(heap))
+		{
+			pairingheap_node *node = pairingheap_first(heap);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			else
+				procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(heap);
+
+			/* Update appropriate flag */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo->inReplayHeap = false;
+			else
+				procInfo->inFlushHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already reached.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for replay LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupReplay(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_REPLAY, currentLSN);
+}
+
+/*
+ * Wake up processes waiting for flush LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupFlush(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedFlushLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_FLUSH, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only possible
+		 * to get a false positive.  But that will be eliminated by a recheck
+		 * inside deleteLSNWaiter().
+		 */
+		if (waitLSNState->procInfos[MyProcNumber].inReplayHeap)
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+		if (waitLSNState->procInfos[MyProcNumber].inFlushHeap)
+			deleteLSNWaiter(WAIT_LSN_FLUSH);
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	/*
+	 * Add our process to the replay waiters heap.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, WAIT_LSN_REPLAY);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = 0;
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			currentLSN = GetXLogReplayRecPtr(NULL);
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory replay heap.  We might
+	 * already be deleted by the startup process.  The 'inReplayHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
+
+/*
+ * Wait until targetLSN has been flushed on a primary server.
+ * Returns only after the condition is satisfied or on FATAL exit.
+ */
+void
+WaitForLSNFlush(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
+
+	/* We can only wait for flush when we are not in recovery */
+	Assert(!RecoveryInProgress());
+
+	/* Quick exit if already flushed */
+	currentLSN = GetFlushRecPtr(NULL);
+	if (targetLSN <= currentLSN)
+		return;
+
+	/* Add to flush waiters */
+	addLSNWaiter(targetLSN, WAIT_LSN_FLUSH);
+
+	/* Wait loop */
+	for (;;)
+	{
+		int			rc;
+
+		/* Check if the waited LSN has been flushed */
+		currentLSN = GetFlushRecPtr(NULL);
+		if (targetLSN <= currentLSN)
+			break;
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died. This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN flush")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory flush heap. We might
+	 * already be deleted by the waker process. The 'inFlushHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_FLUSH);
+
+	return;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..441bf475b4d
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * Wait operation types for LSN waiting facility.
+ */
+typedef enum WaitLSNOperation
+{
+	WAIT_LSN_REPLAY,	/* Waiting for replay on standby */
+	WAIT_LSN_FLUSH		/* Waiting for flush on primary */
+} WaitLSNOperation;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Type-safe heap membership flags */
+	bool		inReplayHeap;	/* In replay waiters heap */
+	bool		inFlushHeap;	/* In flush waiters heap */
+
+	/* Separate heap nodes for type safety */
+	pairingheap_node replayHeapNode;
+	pairingheap_node flushHeapNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum replay LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedReplayLSN;
+
+	/*
+	 * A pairing heap of replay waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap replayWaitersHeap;
+
+	/*
+	 * The minimum flush LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after flushing
+	 * WAL.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedFlushLSN;
+
+	/*
+	 * A pairing heap of flush waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap flushWaitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
+extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern void WaitForLSNFlush(XLogRecPtr targetLSN);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
-- 
2.51.0



  [application/x-patch] v13-0001-Add-pairingheap_initialize-for-shared-memory-usag.patch (3.0K, ../../CABPTF7UUL5SrOddBWG0Ud65YgAOz8aSzfjEjFd_NF94JzfWgxQ@mail.gmail.com/4-v13-0001-Add-pairingheap_initialize-for-shared-memory-usag.patch)
  download | inline diff:
From 48abb92fb33628f6eba5bbe865b3b19c24fb716d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Oct 2025 10:29:05 +0800
Subject: [PATCH v13 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-15 00:23                                         ` Xuneng Zhou <[email protected]>
  2025-10-15 08:40                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-15 00:23 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; +Cc: jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

On Tue, Oct 14, 2025 at 9:03 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Sat, Oct 4, 2025 at 9:35 AM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Sun, Sep 28, 2025 at 5:02 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi Álvaro,
> > > >
> > > > Thanks for your review.
> > > >
> > > > On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
> > > > >
> > > > > On 2025-Sep-15, Alexander Korotkov wrote:
> > > > >
> > > > > > > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > > > > > > PUBLICATION - all use minimal grammar rules that produce generic
> > > > > > > option lists, with the actual interpretation done in their respective
> > > > > > > implementation files. The moderate complexity in wait.c seems
> > > > > > > acceptable.
> > > > >
> > > > > Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> > > > > lists of DefElem (a name optionally followed by a value) instead of
> > > > > individual scattered elements that must later be matched up.  Why not
> > > > > use utility_option_list instead and then loop on the list of DefElems?
> > > > > It'd be a lot simpler.
> > > >
> > > > I took a look at commands like VACUUM and EXPLAIN and they do follow
> > > > this pattern. v11 will make use of utility_option_list.
> > > >
> > > > > Also, we've found that failing to surround the options by parens leads
> > > > > to pain down the road, so maybe add that.  Given that the LSN seems to
> > > > > be mandatory, maybe make it something like
> > > > >
> > > > > WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
> > > > >
> > > > > This requires that you make LSN a keyword, albeit unreserved.  Or you
> > > > > could make it
> > > > > WAIT FOR Ident [the rest]
> > > > > and then ensure in C that the identifier matches the word LSN, such as
> > > > > we do for "permissive" and "restrictive" in
> > > > > RowSecurityDefaultPermissive.
> > > >
> > > > Shall make LSN an unreserved keyword as well.
> > >
> > > Here's the updated v11.  Many thanks Jian for off-list discussions and review.
> >
> > v12 removed unused
> > +WaitStmt
> > +WaitStmtParam in pgindent/typedefs.list.
> >
>
> Hi, I’ve split the patch into multiple patch sets for easier review,
> per Michael’s advice [1].
>
> [1] https://www.postgresql.org/message-id/aOMsv9TszlB1n-W7%40paquier.xyz
>

Patch 2 in v13 is corrupted and patch 3 has an error. Sorry for the
noise. Here's v14.

Best,
Xuneng


Attachments:

  [application/octet-stream] v14-0003-Implement-WAIT-FOR-command.patch (47.0K, ../../CABPTF7X8P42TKwoLt2WsjXxqtCh6S_goK_6st8Wov=g+yA6n4A@mail.gmail.com/2-v14-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 40b49e1f21ab0af763e2875614a5105bad4fb2f6 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 14 Oct 2025 22:46:31 +0800
Subject: [PATCH v14] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Co-authored-by: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/access/transam/xlogwait.c     |  24 +-
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 ++++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/access/xlogwait.h             |   3 +-
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 293 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   3 +
 23 files changed, 948 insertions(+), 14 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..8df1f2ab953
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ [WITH] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..b5e07a724f5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6225,6 +6226,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeupReplay(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..f848ac8a77d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN)))
+				 WaitLSNWakeupReplay(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 621f790bbdb..c5d269d6e06 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -364,9 +364,10 @@ WaitLSNCleanup(void)
  * or replica got promoted before the target LSN replayed.
  */
 WaitLSNResult
-WaitForLSNReplay(XLogRecPtr targetLSN)
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
 {
 	XLogRecPtr	currentLSN;
+	TimestampTz	endtime = 0;
 	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
 
 	/* Shouldn't be called when shmem isn't initialized */
@@ -375,6 +376,11 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	/* Should have a valid proc number */
 	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
 
+	if (timeout > 0) {
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
 	/*
 	 * Add our process to the replay waiters heap.  It might happen that
 	 * target LSN gets replayed before we do.  Another check at the beginning
@@ -385,6 +391,7 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	for (;;)
 	{
 		int			rc;
+		long		delay_ms = 0;
 		currentLSN = GetXLogReplayRecPtr(NULL);
 
 		/* Recheck that recovery is still in-progress */
@@ -407,9 +414,16 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 				break;
 		}
 
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch, wake_events, -1,
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
 					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
 
 		/*
@@ -433,6 +447,12 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	 */
 	deleteLSNWaiter(WAIT_LSN_REPLAY);
 
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
 	return WAIT_LSN_RESULT_SUCCESS;
 }
 
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 21caf2d43bf..1d016df1f6b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16453,6 +16454,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			opt_with '(' utility_option_list ')'	{ $$ = $3; }
+			| /*EMPTY*/							    { $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17940,6 +17961,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18110,6 +18132,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18556,6 +18579,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18767,6 +18791,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 441bf475b4d..2e33a1d22d0 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -27,6 +27,7 @@ typedef enum
 	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
 	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
 										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
 } WaitLSNResult;
 
 /*
@@ -106,7 +107,7 @@ extern void WaitLSNShmemInit(void);
 extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
 extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
-extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
 extern void WaitForLSNFlush(XLogRecPtr targetLSN);
 
 #endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc09d1a3f03..c741099e186 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4384,4 +4384,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..62fdc7cd06c
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,293 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..a2c93c0ef4e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3263,6 +3263,9 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



  [application/octet-stream] v14-0001-Add-pairingheap_initialize-for-shared-memory-usag.patch (3.0K, ../../CABPTF7X8P42TKwoLt2WsjXxqtCh6S_goK_6st8Wov=g+yA6n4A@mail.gmail.com/3-v14-0001-Add-pairingheap_initialize-for-shared-memory-usag.patch)
  download | inline diff:
From 48abb92fb33628f6eba5bbe865b3b19c24fb716d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Oct 2025 10:29:05 +0800
Subject: [PATCH v14 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



  [application/octet-stream] v14-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (24.7K, ../../CABPTF7X8P42TKwoLt2WsjXxqtCh6S_goK_6st8Wov=g+yA6n4A@mail.gmail.com/4-v14-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 645e19b2d0d522c16eb731da527baf18f73a7ec2 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 14 Oct 2025 22:12:23 +0800
Subject: [PATCH v14 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 503 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 | 112 ++++
 src/include/storage/lwlocklist.h              |   1 +
 7 files changed, 625 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..621f790bbdb
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,503 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+static int	waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		/* Initialize replay heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedReplayLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->replayWaitersHeap, waitlsn_replay_cmp, (void *)(uintptr_t)WAIT_LSN_REPLAY);
+
+		/* Initialize flush heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedFlushLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->flushWaitersHeap, waitlsn_flush_cmp, (void *)(uintptr_t)WAIT_LSN_FLUSH);
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for replay waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Comparison function for flush waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNOperation operation)
+{
+	XLogRecPtr minWaitedLSN = PG_UINT64_MAX;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		if (!pairingheap_is_empty(&waitLSNState->replayWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->replayWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedReplayLSN, minWaitedLSN);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		if (!pairingheap_is_empty(&waitLSNState->flushWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->flushWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedFlushLSN, minWaitedLSN);
+	}
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		Assert(!procInfo->inReplayHeap);
+		pairingheap_add(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		Assert(!procInfo->inFlushHeap);
+		pairingheap_add(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (operation == WAIT_LSN_REPLAY && procInfo->inReplayHeap)
+	{
+		pairingheap_remove(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else if (operation == WAIT_LSN_FLUSH && procInfo->inFlushHeap)
+	{
+		pairingheap_remove(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNOperation operation, XLogRecPtr currentLSN)
+{
+	ProcNumber wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int numWakeUpProcs;
+	int i;
+	pairingheap *heap;
+
+	/* Select appropriate heap */
+	heap = (operation == WAIT_LSN_REPLAY) ?
+		   &waitLSNState->replayWaitersHeap :
+		   &waitLSNState->flushWaitersHeap;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached.
+		 * Record process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(heap))
+		{
+			pairingheap_node *node = pairingheap_first(heap);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			else
+				procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(heap);
+
+			/* Update appropriate flag */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo->inReplayHeap = false;
+			else
+				procInfo->inFlushHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already reached.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for replay LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupReplay(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_REPLAY, currentLSN);
+}
+
+/*
+ * Wake up processes waiting for flush LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupFlush(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedFlushLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_FLUSH, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only possible
+		 * to get a false positive.  But that will be eliminated by a recheck
+		 * inside deleteLSNWaiter().
+		 */
+		if (waitLSNState->procInfos[MyProcNumber].inReplayHeap)
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+		if (waitLSNState->procInfos[MyProcNumber].inFlushHeap)
+			deleteLSNWaiter(WAIT_LSN_FLUSH);
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	/*
+	 * Add our process to the replay waiters heap.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, WAIT_LSN_REPLAY);
+
+	for (;;)
+	{
+		int			rc;
+		currentLSN = GetXLogReplayRecPtr(NULL);
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory replay heap.  We might
+	 * already be deleted by the startup process.  The 'inReplayHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
+
+/*
+ * Wait until targetLSN has been flushed on a primary server.
+ * Returns only after the condition is satisfied or on FATAL exit.
+ */
+void
+WaitForLSNFlush(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
+
+	/* We can only wait for flush when we are not in recovery */
+	Assert(!RecoveryInProgress());
+
+	/* Quick exit if already flushed */
+	currentLSN = GetFlushRecPtr(NULL);
+	if (targetLSN <= currentLSN)
+		return;
+
+	/* Add to flush waiters */
+	addLSNWaiter(targetLSN, WAIT_LSN_FLUSH);
+
+	/* Wait loop */
+	for (;;)
+	{
+		int			rc;
+
+		/* Check if the waited LSN has been flushed */
+		currentLSN = GetFlushRecPtr(NULL);
+		if (targetLSN <= currentLSN)
+			break;
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died. This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN flush")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory flush heap. We might
+	 * already be deleted by the waker process. The 'inFlushHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_FLUSH);
+
+	return;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..441bf475b4d
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "postgres.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * Wait operation types for LSN waiting facility.
+ */
+typedef enum WaitLSNOperation
+{
+	WAIT_LSN_REPLAY,	/* Waiting for replay on standby */
+	WAIT_LSN_FLUSH		/* Waiting for flush on primary */
+} WaitLSNOperation;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Type-safe heap membership flags */
+	bool		inReplayHeap;	/* In replay waiters heap */
+	bool		inFlushHeap;	/* In flush waiters heap */
+
+	/* Separate heap nodes for type safety */
+	pairingheap_node replayHeapNode;
+	pairingheap_node flushHeapNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum replay LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedReplayLSN;
+
+	/*
+	 * A pairing heap of replay waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap replayWaitersHeap;
+
+	/*
+	 * The minimum flush LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after flushing
+	 * WAL.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedFlushLSN;
+
+	/*
+	 * A pairing heap of flush waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap flushWaitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
+extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern void WaitForLSNFlush(XLogRecPtr targetLSN);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-15 08:40                                           ` Xuneng Zhou <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-15 08:40 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; +Cc: jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

On Wed, Oct 15, 2025 at 8:23 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Tue, Oct 14, 2025 at 9:03 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Sat, Oct 4, 2025 at 9:35 AM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Sun, Sep 28, 2025 at 5:02 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi,
> > > >
> > > > On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
> > > > >
> > > > > Hi Álvaro,
> > > > >
> > > > > Thanks for your review.
> > > > >
> > > > > On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
> > > > > >
> > > > > > On 2025-Sep-15, Alexander Korotkov wrote:
> > > > > >
> > > > > > > > It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> > > > > > > > PUBLICATION - all use minimal grammar rules that produce generic
> > > > > > > > option lists, with the actual interpretation done in their respective
> > > > > > > > implementation files. The moderate complexity in wait.c seems
> > > > > > > > acceptable.
> > > > > >
> > > > > > Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> > > > > > lists of DefElem (a name optionally followed by a value) instead of
> > > > > > individual scattered elements that must later be matched up.  Why not
> > > > > > use utility_option_list instead and then loop on the list of DefElems?
> > > > > > It'd be a lot simpler.
> > > > >
> > > > > I took a look at commands like VACUUM and EXPLAIN and they do follow
> > > > > this pattern. v11 will make use of utility_option_list.
> > > > >
> > > > > > Also, we've found that failing to surround the options by parens leads
> > > > > > to pain down the road, so maybe add that.  Given that the LSN seems to
> > > > > > be mandatory, maybe make it something like
> > > > > >
> > > > > > WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
> > > > > >
> > > > > > This requires that you make LSN a keyword, albeit unreserved.  Or you
> > > > > > could make it
> > > > > > WAIT FOR Ident [the rest]
> > > > > > and then ensure in C that the identifier matches the word LSN, such as
> > > > > > we do for "permissive" and "restrictive" in
> > > > > > RowSecurityDefaultPermissive.
> > > > >
> > > > > Shall make LSN an unreserved keyword as well.
> > > >
> > > > Here's the updated v11.  Many thanks Jian for off-list discussions and review.
> > >
> > > v12 removed unused
> > > +WaitStmt
> > > +WaitStmtParam in pgindent/typedefs.list.
> > >
> >
> > Hi, I’ve split the patch into multiple patch sets for easier review,
> > per Michael’s advice [1].
> >
> > [1] https://www.postgresql.org/message-id/aOMsv9TszlB1n-W7%40paquier.xyz
> >
>
> Patch 2 in v13 is corrupted and patch 3 has an error. Sorry for the
> noise. Here's v14.
>

Made minor changes to #include of xlogwait.h in patch2 to calm CF-bots down.

Best,
Xuneng


Attachments:

  [application/octet-stream] v15-0001-Add-pairingheap_initialize-for-shared-memory-usag copy.patch (3.0K, ../../CABPTF7XqJ_3EwGXjJhCwW0+gYWvQX070y=fRCS17yrxLpcgVoA@mail.gmail.com/2-v15-0001-Add-pairingheap_initialize-for-shared-memory-usag%20copy.patch)
  download | inline diff:
From 48abb92fb33628f6eba5bbe865b3b19c24fb716d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Oct 2025 10:29:05 +0800
Subject: [PATCH v15 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



  [application/octet-stream] v15-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (24.7K, ../../CABPTF7XqJ_3EwGXjJhCwW0+gYWvQX070y=fRCS17yrxLpcgVoA@mail.gmail.com/3-v15-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 39857e15fac0a7b5b3105b730db4dfb271788cca Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 15 Oct 2025 15:47:27 +0800
Subject: [PATCH v15] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 503 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 | 112 ++++
 src/include/storage/lwlocklist.h              |   1 +
 7 files changed, 625 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..49dae7ac1c4
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,503 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+static int	waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		/* Initialize replay heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedReplayLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->replayWaitersHeap, waitlsn_replay_cmp, (void *)(uintptr_t)WAIT_LSN_REPLAY);
+
+		/* Initialize flush heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedFlushLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->flushWaitersHeap, waitlsn_flush_cmp, (void *)(uintptr_t)WAIT_LSN_FLUSH);
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for replay waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Comparison function for flush waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNOperation operation)
+{
+	XLogRecPtr minWaitedLSN = PG_UINT64_MAX;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		if (!pairingheap_is_empty(&waitLSNState->replayWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->replayWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedReplayLSN, minWaitedLSN);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		if (!pairingheap_is_empty(&waitLSNState->flushWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->flushWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedFlushLSN, minWaitedLSN);
+	}
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		Assert(!procInfo->inReplayHeap);
+		pairingheap_add(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		Assert(!procInfo->inFlushHeap);
+		pairingheap_add(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (operation == WAIT_LSN_REPLAY && procInfo->inReplayHeap)
+	{
+		pairingheap_remove(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else if (operation == WAIT_LSN_FLUSH && procInfo->inFlushHeap)
+	{
+		pairingheap_remove(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNOperation operation, XLogRecPtr currentLSN)
+{
+	ProcNumber wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int numWakeUpProcs;
+	int i;
+	pairingheap *heap;
+
+	/* Select appropriate heap */
+	heap = (operation == WAIT_LSN_REPLAY) ?
+		   &waitLSNState->replayWaitersHeap :
+		   &waitLSNState->flushWaitersHeap;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached.
+		 * Record process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(heap))
+		{
+			pairingheap_node *node = pairingheap_first(heap);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			else
+				procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(heap);
+
+			/* Update appropriate flag */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo->inReplayHeap = false;
+			else
+				procInfo->inFlushHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already reached.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for replay LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupReplay(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_REPLAY, currentLSN);
+}
+
+/*
+ * Wake up processes waiting for flush LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupFlush(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedFlushLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_FLUSH, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only possible
+		 * to get a false positive.  But that will be eliminated by a recheck
+		 * inside deleteLSNWaiter().
+		 */
+		if (waitLSNState->procInfos[MyProcNumber].inReplayHeap)
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+		if (waitLSNState->procInfos[MyProcNumber].inFlushHeap)
+			deleteLSNWaiter(WAIT_LSN_FLUSH);
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	/*
+	 * Add our process to the replay waiters heap.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, WAIT_LSN_REPLAY);
+
+	for (;;)
+	{
+		int			rc;
+		currentLSN = GetXLogReplayRecPtr(NULL);
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory replay heap.  We might
+	 * already be deleted by the startup process.  The 'inReplayHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
+
+/*
+ * Wait until targetLSN has been flushed on a primary server.
+ * Returns only after the condition is satisfied or on FATAL exit.
+ */
+void
+WaitForLSNFlush(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
+
+	/* We can only wait for flush when we are not in recovery */
+	Assert(!RecoveryInProgress());
+
+	/* Quick exit if already flushed */
+	currentLSN = GetFlushRecPtr(NULL);
+	if (targetLSN <= currentLSN)
+		return;
+
+	/* Add to flush waiters */
+	addLSNWaiter(targetLSN, WAIT_LSN_FLUSH);
+
+	/* Wait loop */
+	for (;;)
+	{
+		int			rc;
+
+		/* Check if the waited LSN has been flushed */
+		currentLSN = GetFlushRecPtr(NULL);
+		if (targetLSN <= currentLSN)
+			break;
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died. This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN flush")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory flush heap. We might
+	 * already be deleted by the waker process. The 'inFlushHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_FLUSH);
+
+	return;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..ada2a460ca4
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * Wait operation types for LSN waiting facility.
+ */
+typedef enum WaitLSNOperation
+{
+	WAIT_LSN_REPLAY,	/* Waiting for replay on standby */
+	WAIT_LSN_FLUSH		/* Waiting for flush on primary */
+} WaitLSNOperation;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Type-safe heap membership flags */
+	bool		inReplayHeap;	/* In replay waiters heap */
+	bool		inFlushHeap;	/* In flush waiters heap */
+
+	/* Separate heap nodes for type safety */
+	pairingheap_node replayHeapNode;
+	pairingheap_node flushHeapNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum replay LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedReplayLSN;
+
+	/*
+	 * A pairing heap of replay waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap replayWaitersHeap;
+
+	/*
+	 * The minimum flush LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after flushing
+	 * WAL.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedFlushLSN;
+
+	/*
+	 * A pairing heap of flush waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap flushWaitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
+extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern void WaitForLSNFlush(XLogRecPtr targetLSN);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
-- 
2.51.0



  [application/octet-stream] v15-0003-Implement-WAIT-FOR-command.patch (47.0K, ../../CABPTF7XqJ_3EwGXjJhCwW0+gYWvQX070y=fRCS17yrxLpcgVoA@mail.gmail.com/4-v15-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 72b1c2063710693b1976268e8be99a74a8533956 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 15 Oct 2025 16:03:49 +0800
Subject: [PATCH v15] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Co-authored-by: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/access/transam/xlogwait.c     |  24 +-
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 ++++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/access/xlogwait.h             |   3 +-
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 293 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   3 +
 23 files changed, 948 insertions(+), 14 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..8df1f2ab953
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ [WITH] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..b5e07a724f5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6225,6 +6226,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeupReplay(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..f848ac8a77d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN)))
+				 WaitLSNWakeupReplay(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 49dae7ac1c4..2f5f8eaf583 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -364,9 +364,10 @@ WaitLSNCleanup(void)
  * or replica got promoted before the target LSN replayed.
  */
 WaitLSNResult
-WaitForLSNReplay(XLogRecPtr targetLSN)
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
 {
 	XLogRecPtr	currentLSN;
+	TimestampTz	endtime = 0;
 	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
 
 	/* Shouldn't be called when shmem isn't initialized */
@@ -375,6 +376,11 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	/* Should have a valid proc number */
 	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
 
+	if (timeout > 0) {
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
 	/*
 	 * Add our process to the replay waiters heap.  It might happen that
 	 * target LSN gets replayed before we do.  Another check at the beginning
@@ -385,6 +391,7 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	for (;;)
 	{
 		int			rc;
+		long		delay_ms = 0;
 		currentLSN = GetXLogReplayRecPtr(NULL);
 
 		/* Recheck that recovery is still in-progress */
@@ -407,9 +414,16 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 				break;
 		}
 
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch, wake_events, -1,
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
 					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
 
 		/*
@@ -433,6 +447,12 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	 */
 	deleteLSNWaiter(WAIT_LSN_REPLAY);
 
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
 	return WAIT_LSN_RESULT_SUCCESS;
 }
 
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dc0c2886674..c9e0738724b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16453,6 +16454,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			opt_with '(' utility_option_list ')'	{ $$ = $3; }
+			| /*EMPTY*/							    { $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17940,6 +17961,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18110,6 +18132,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18556,6 +18579,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18767,6 +18791,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index ada2a460ca4..28aea61f6a2 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -27,6 +27,7 @@ typedef enum
 	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
 	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
 										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
 } WaitLSNResult;
 
 /*
@@ -106,7 +107,7 @@ extern void WaitLSNShmemInit(void);
 extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
 extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
-extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
 extern void WaitForLSNFlush(XLogRecPtr targetLSN);
 
 #endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4e445fe0cd7..75c41ad0cb9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4384,4 +4384,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..62fdc7cd06c
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,293 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..a2c93c0ef4e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3263,6 +3263,9 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-15 08:51                                           ` Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Álvaro Herrera @ 2025-10-15 08:51 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

I didn't review the patch other than look at the grammar, but I disagree
with using opt_with in it.  I think WITH should be a mandatory word, or
just not be there at all.  The current formulation lets you do one of:

1. WAIT FOR LSN '123/456' WITH (opt = val);
2. WAIT FOR LSN '123/456' (opt = val);
3. WAIT FOR LSN '123/456';

and I don't see why you need two ways to specify an option list.

So one option is to remove opt_wait_with_clause and just use
opt_utility_option_list, which would remove the WITH keyword from there
(ie. only keep 2 and 3 from the above list).  But I think that's worse:
just look at the REPACK grammar[1], where we have to have additional
productions for the optional parenthesized option list.

So why not do just

+opt_wait_with_clause:
+           WITH '(' utility_option_list ')'        { $$ = $3; }
+           | /*EMPTY*/                             { $$ = NIL; }
+           ;

which keeps options 1 and 3 of the list above.

Note: you don't need to worry about WITH_LA, because that's only going
to show up when the user writes WITH TIME or WITH ORDINALITY (see
parser.c), and that's a syntax error anyway.


[1] https://postgr.es/m/[email protected]

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"La virtud es el justo medio entre dos defectos" (Aristóteles)





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2025-10-15 12:48                                             ` Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-15 12:48 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

Thank you for the grammar review and the clear recommendation.

On Wed, Oct 15, 2025 at 4:51 PM Álvaro Herrera <[email protected]> wrote:
>
> I didn't review the patch other than look at the grammar, but I disagree
> with using opt_with in it.  I think WITH should be a mandatory word, or
> just not be there at all.  The current formulation lets you do one of:
>
> 1. WAIT FOR LSN '123/456' WITH (opt = val);
> 2. WAIT FOR LSN '123/456' (opt = val);
> 3. WAIT FOR LSN '123/456';
>
> and I don't see why you need two ways to specify an option list.

I agree with this as unnecessary choices are confusing.

>
> So one option is to remove opt_wait_with_clause and just use
> opt_utility_option_list, which would remove the WITH keyword from there
> (ie. only keep 2 and 3 from the above list).  But I think that's worse:
> just look at the REPACK grammar[1], where we have to have additional
> productions for the optional parenthesized option list.
>
>
>
> So why not do just
>
> +opt_wait_with_clause:
> +           WITH '(' utility_option_list ')'        { $$ = $3; }
> +           | /*EMPTY*/                             { $$ = NIL; }
> +           ;
>
> which keeps options 1 and 3 of the list above.

Your suggested approach of making WITH mandatory when options are
present looks better.
I've implemented the change as you recommended. Please see patch 3 in v16.

>
>
>
> Note: you don't need to worry about WITH_LA, because that's only going
> to show up when the user writes WITH TIME or WITH ORDINALITY (see
> parser.c), and that's a syntax error anyway.
>

Yeah, we require '(' immediately after WITH in our grammar, the
lookahead mechanism will keep it as regular WITH, and any attempt to
write "WITH TIME" or "WITH ORDINALITY" would be a syntax error anyway,
which is expected.

Best,
Xuneng


Attachments:

  [application/octet-stream] v16-0001-Add-pairingheap_initialize-for-shared-memory-usag copy.patch (3.0K, ../../CABPTF7VzW-TznkwcZZozg5vHTUfdsw03-rsmLtTFOQBFQdfYPw@mail.gmail.com/2-v16-0001-Add-pairingheap_initialize-for-shared-memory-usag%20copy.patch)
  download | inline diff:
From 48abb92fb33628f6eba5bbe865b3b19c24fb716d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Oct 2025 10:29:05 +0800
Subject: [PATCH v16 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



  [application/octet-stream] v16-0003-Implement-WAIT-FOR-command.patch (47.3K, ../../CABPTF7VzW-TznkwcZZozg5vHTUfdsw03-rsmLtTFOQBFQdfYPw@mail.gmail.com/3-v16-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 38971b2448786de5f58ba9be088d4e7e8fc11987 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 15 Oct 2025 16:03:49 +0800
Subject: [PATCH v16 3/3] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Co-authored-by: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>

---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/access/transam/xlogwait.c     |  24 +-
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 +++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/access/xlogwait.h             |   3 +-
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 301 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   3 +
 23 files changed, 956 insertions(+), 14 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..3b8e842d1de
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..b5e07a724f5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6225,6 +6226,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeupReplay(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..f848ac8a77d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN)))
+				 WaitLSNWakeupReplay(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 49dae7ac1c4..2f5f8eaf583 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -364,9 +364,10 @@ WaitLSNCleanup(void)
  * or replica got promoted before the target LSN replayed.
  */
 WaitLSNResult
-WaitForLSNReplay(XLogRecPtr targetLSN)
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
 {
 	XLogRecPtr	currentLSN;
+	TimestampTz	endtime = 0;
 	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
 
 	/* Shouldn't be called when shmem isn't initialized */
@@ -375,6 +376,11 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	/* Should have a valid proc number */
 	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
 
+	if (timeout > 0) {
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
 	/*
 	 * Add our process to the replay waiters heap.  It might happen that
 	 * target LSN gets replayed before we do.  Another check at the beginning
@@ -385,6 +391,7 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	for (;;)
 	{
 		int			rc;
+		long		delay_ms = 0;
 		currentLSN = GetXLogReplayRecPtr(NULL);
 
 		/* Recheck that recovery is still in-progress */
@@ -407,9 +414,16 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 				break;
 		}
 
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch, wake_events, -1,
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
 					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
 
 		/*
@@ -433,6 +447,12 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	 */
 	deleteLSNWaiter(WAIT_LSN_REPLAY);
 
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
 	return WAIT_LSN_RESULT_SUCCESS;
 }
 
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dc0c2886674..bec885ea73e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16453,6 +16454,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			WITH '(' utility_option_list ')'		{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17940,6 +17961,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18110,6 +18132,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18556,6 +18579,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18767,6 +18791,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index ada2a460ca4..28aea61f6a2 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -27,6 +27,7 @@ typedef enum
 	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
 	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
 										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
 } WaitLSNResult;
 
 /*
@@ -106,7 +107,7 @@ extern void WaitLSNShmemInit(void);
 extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
 extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
-extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
 extern void WaitForLSNFlush(XLogRecPtr targetLSN);
 
 #endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4e445fe0cd7..75c41ad0cb9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4384,4 +4384,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..cc709670e09
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,301 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - options without WITH keyword
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' (timeout '100ms');",
+	stderr => \$stderr);
+ok($stderr =~ /syntax error/,
+	"get syntax error when options specified without WITH keyword");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..a2c93c0ef4e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3263,6 +3263,9 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



  [application/octet-stream] v16-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (24.7K, ../../CABPTF7VzW-TznkwcZZozg5vHTUfdsw03-rsmLtTFOQBFQdfYPw@mail.gmail.com/4-v16-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 39857e15fac0a7b5b3105b730db4dfb271788cca Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 15 Oct 2025 15:47:27 +0800
Subject: [PATCH v16 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 503 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 | 112 ++++
 src/include/storage/lwlocklist.h              |   1 +
 7 files changed, 625 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..49dae7ac1c4
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,503 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+static int	waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		/* Initialize replay heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedReplayLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->replayWaitersHeap, waitlsn_replay_cmp, (void *)(uintptr_t)WAIT_LSN_REPLAY);
+
+		/* Initialize flush heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedFlushLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->flushWaitersHeap, waitlsn_flush_cmp, (void *)(uintptr_t)WAIT_LSN_FLUSH);
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for replay waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Comparison function for flush waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNOperation operation)
+{
+	XLogRecPtr minWaitedLSN = PG_UINT64_MAX;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		if (!pairingheap_is_empty(&waitLSNState->replayWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->replayWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedReplayLSN, minWaitedLSN);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		if (!pairingheap_is_empty(&waitLSNState->flushWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->flushWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedFlushLSN, minWaitedLSN);
+	}
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		Assert(!procInfo->inReplayHeap);
+		pairingheap_add(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		Assert(!procInfo->inFlushHeap);
+		pairingheap_add(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (operation == WAIT_LSN_REPLAY && procInfo->inReplayHeap)
+	{
+		pairingheap_remove(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else if (operation == WAIT_LSN_FLUSH && procInfo->inFlushHeap)
+	{
+		pairingheap_remove(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNOperation operation, XLogRecPtr currentLSN)
+{
+	ProcNumber wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int numWakeUpProcs;
+	int i;
+	pairingheap *heap;
+
+	/* Select appropriate heap */
+	heap = (operation == WAIT_LSN_REPLAY) ?
+		   &waitLSNState->replayWaitersHeap :
+		   &waitLSNState->flushWaitersHeap;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached.
+		 * Record process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(heap))
+		{
+			pairingheap_node *node = pairingheap_first(heap);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			else
+				procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(heap);
+
+			/* Update appropriate flag */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo->inReplayHeap = false;
+			else
+				procInfo->inFlushHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already reached.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for replay LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupReplay(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_REPLAY, currentLSN);
+}
+
+/*
+ * Wake up processes waiting for flush LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupFlush(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedFlushLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_FLUSH, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only possible
+		 * to get a false positive.  But that will be eliminated by a recheck
+		 * inside deleteLSNWaiter().
+		 */
+		if (waitLSNState->procInfos[MyProcNumber].inReplayHeap)
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+		if (waitLSNState->procInfos[MyProcNumber].inFlushHeap)
+			deleteLSNWaiter(WAIT_LSN_FLUSH);
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	/*
+	 * Add our process to the replay waiters heap.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, WAIT_LSN_REPLAY);
+
+	for (;;)
+	{
+		int			rc;
+		currentLSN = GetXLogReplayRecPtr(NULL);
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory replay heap.  We might
+	 * already be deleted by the startup process.  The 'inReplayHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
+
+/*
+ * Wait until targetLSN has been flushed on a primary server.
+ * Returns only after the condition is satisfied or on FATAL exit.
+ */
+void
+WaitForLSNFlush(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
+
+	/* We can only wait for flush when we are not in recovery */
+	Assert(!RecoveryInProgress());
+
+	/* Quick exit if already flushed */
+	currentLSN = GetFlushRecPtr(NULL);
+	if (targetLSN <= currentLSN)
+		return;
+
+	/* Add to flush waiters */
+	addLSNWaiter(targetLSN, WAIT_LSN_FLUSH);
+
+	/* Wait loop */
+	for (;;)
+	{
+		int			rc;
+
+		/* Check if the waited LSN has been flushed */
+		currentLSN = GetFlushRecPtr(NULL);
+		if (targetLSN <= currentLSN)
+			break;
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died. This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN flush")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory flush heap. We might
+	 * already be deleted by the waker process. The 'inFlushHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_FLUSH);
+
+	return;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..ada2a460ca4
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * Wait operation types for LSN waiting facility.
+ */
+typedef enum WaitLSNOperation
+{
+	WAIT_LSN_REPLAY,	/* Waiting for replay on standby */
+	WAIT_LSN_FLUSH		/* Waiting for flush on primary */
+} WaitLSNOperation;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Type-safe heap membership flags */
+	bool		inReplayHeap;	/* In replay waiters heap */
+	bool		inFlushHeap;	/* In flush waiters heap */
+
+	/* Separate heap nodes for type safety */
+	pairingheap_node replayHeapNode;
+	pairingheap_node flushHeapNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum replay LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedReplayLSN;
+
+	/*
+	 * A pairing heap of replay waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap replayWaitersHeap;
+
+	/*
+	 * The minimum flush LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after flushing
+	 * WAL.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedFlushLSN;
+
+	/*
+	 * A pairing heap of flush waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap flushWaitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
+extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern void WaitForLSNFlush(XLogRecPtr targetLSN);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-16 07:11                                               ` Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-16 07:11 UTC (permalink / raw)
  To: pgsql-hackers <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; Álvaro Herrera <[email protected]>

Hi,

On Wed, Oct 15, 2025 at 8:48 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> Thank you for the grammar review and the clear recommendation.
>
> On Wed, Oct 15, 2025 at 4:51 PM Álvaro Herrera <[email protected]> wrote:
> >
> > I didn't review the patch other than look at the grammar, but I disagree
> > with using opt_with in it.  I think WITH should be a mandatory word, or
> > just not be there at all.  The current formulation lets you do one of:
> >
> > 1. WAIT FOR LSN '123/456' WITH (opt = val);
> > 2. WAIT FOR LSN '123/456' (opt = val);
> > 3. WAIT FOR LSN '123/456';
> >
> > and I don't see why you need two ways to specify an option list.
>
> I agree with this as unnecessary choices are confusing.
>
> >
> > So one option is to remove opt_wait_with_clause and just use
> > opt_utility_option_list, which would remove the WITH keyword from there
> > (ie. only keep 2 and 3 from the above list).  But I think that's worse:
> > just look at the REPACK grammar[1], where we have to have additional
> > productions for the optional parenthesized option list.
> >
> >
> >
> > So why not do just
> >
> > +opt_wait_with_clause:
> > +           WITH '(' utility_option_list ')'        { $$ = $3; }
> > +           | /*EMPTY*/                             { $$ = NIL; }
> > +           ;
> >
> > which keeps options 1 and 3 of the list above.
>
> Your suggested approach of making WITH mandatory when options are
> present looks better.
> I've implemented the change as you recommended. Please see patch 3 in v16.
>
> >
> >
> >
> > Note: you don't need to worry about WITH_LA, because that's only going
> > to show up when the user writes WITH TIME or WITH ORDINALITY (see
> > parser.c), and that's a syntax error anyway.
> >
>
> Yeah, we require '(' immediately after WITH in our grammar, the
> lookahead mechanism will keep it as regular WITH, and any attempt to
> write "WITH TIME" or "WITH ORDINALITY" would be a syntax error anyway,
> which is expected.
>

The filename of patch 1 is incorrect due to coping. Just correct it.

Best,
Xuneng


Attachments:

  [application/octet-stream] v16-0001-Add-pairingheap_initialize-for-shared-memory-usag.patch (3.0K, ../../CABPTF7V4yxMvLevcs4tvGR54-AS-o9L5J8s8W4M3QS8eMsQb+w@mail.gmail.com/2-v16-0001-Add-pairingheap_initialize-for-shared-memory-usag.patch)
  download | inline diff:
From 48abb92fb33628f6eba5bbe865b3b19c24fb716d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Oct 2025 10:29:05 +0800
Subject: [PATCH v16 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



  [application/octet-stream] v16-0003-Implement-WAIT-FOR-command.patch (47.3K, ../../CABPTF7V4yxMvLevcs4tvGR54-AS-o9L5J8s8W4M3QS8eMsQb+w@mail.gmail.com/3-v16-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 38971b2448786de5f58ba9be088d4e7e8fc11987 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 15 Oct 2025 16:03:49 +0800
Subject: [PATCH v16 3/3] Implement WAIT FOR command

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Co-authored-by: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>

---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/access/transam/xlogwait.c     |  24 +-
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 +++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/access/xlogwait.h             |   3 +-
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 301 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   3 +
 23 files changed, 956 insertions(+), 14 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..3b8e842d1de
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..b5e07a724f5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6225,6 +6226,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeupReplay(InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..f848ac8a77d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN)))
+				 WaitLSNWakeupReplay(XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 49dae7ac1c4..2f5f8eaf583 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -364,9 +364,10 @@ WaitLSNCleanup(void)
  * or replica got promoted before the target LSN replayed.
  */
 WaitLSNResult
-WaitForLSNReplay(XLogRecPtr targetLSN)
+WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout)
 {
 	XLogRecPtr	currentLSN;
+	TimestampTz	endtime = 0;
 	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
 
 	/* Shouldn't be called when shmem isn't initialized */
@@ -375,6 +376,11 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	/* Should have a valid proc number */
 	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
 
+	if (timeout > 0) {
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
 	/*
 	 * Add our process to the replay waiters heap.  It might happen that
 	 * target LSN gets replayed before we do.  Another check at the beginning
@@ -385,6 +391,7 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	for (;;)
 	{
 		int			rc;
+		long		delay_ms = 0;
 		currentLSN = GetXLogReplayRecPtr(NULL);
 
 		/* Recheck that recovery is still in-progress */
@@ -407,9 +414,16 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 				break;
 		}
 
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch, wake_events, -1,
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
 					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
 
 		/*
@@ -433,6 +447,12 @@ WaitForLSNReplay(XLogRecPtr targetLSN)
 	 */
 	deleteLSNWaiter(WAIT_LSN_REPLAY);
 
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
 	return WAIT_LSN_RESULT_SUCCESS;
 }
 
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..44db2d71164
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char       *timeout_str;
+			const char *hintmsg;
+			double      result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round
+			 * into range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+							defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why
+	 * WAIT FOR is a command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSNReplay(lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dc0c2886674..bec885ea73e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16453,6 +16454,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			WITH '(' utility_option_list ')'		{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17940,6 +17961,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18110,6 +18132,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18556,6 +18579,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18767,6 +18791,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index ada2a460ca4..28aea61f6a2 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -27,6 +27,7 @@ typedef enum
 	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
 	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
 										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT,	/* Timeout occurred */
 } WaitLSNResult;
 
 /*
@@ -106,7 +107,7 @@ extern void WaitLSNShmemInit(void);
 extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
 extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
-extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN, int64 timeout);
 extern void WaitForLSNFlush(XLogRecPtr targetLSN);
 
 #endif							/* XLOG_WAIT_H */
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4e445fe0cd7..75c41ad0cb9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4384,4 +4384,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..cc709670e09
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,301 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/,
+	"get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/, "get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - options without WITH keyword
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' (timeout '100ms');",
+	stderr => \$stderr);
+ok($stderr =~ /syntax error/,
+	"get syntax error when options specified without WITH keyword");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok( $stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout", "WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..a2c93c0ef4e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3263,6 +3263,9 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNProcInfo
+WaitLSNResult
+WaitLSNState
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



  [application/octet-stream] v16-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (24.7K, ../../CABPTF7V4yxMvLevcs4tvGR54-AS-o9L5J8s8W4M3QS8eMsQb+w@mail.gmail.com/4-v16-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 39857e15fac0a7b5b3105b730db4dfb271788cca Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 15 Oct 2025 15:47:27 +0800
Subject: [PATCH v16 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion:
https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com

Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>

Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 503 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 | 112 ++++
 src/include/storage/lwlocklist.h              |   1 +
 7 files changed, 625 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..49dae7ac1c4
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,503 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+static int	waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+														  WaitLSNShmemSize(),
+														  &found);
+	if (!found)
+	{
+		/* Initialize replay heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedReplayLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->replayWaitersHeap, waitlsn_replay_cmp, (void *)(uintptr_t)WAIT_LSN_REPLAY);
+
+		/* Initialize flush heap and tracking */
+		pg_atomic_init_u64(&waitLSNState->minWaitedFlushLSN, PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->flushWaitersHeap, waitlsn_flush_cmp, (void *)(uintptr_t)WAIT_LSN_FLUSH);
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for replay waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_replay_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, replayHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Comparison function for flush waiters heaps. Waiting processes are
+ * ordered by LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_flush_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, flushHeapNode, b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNOperation operation)
+{
+	XLogRecPtr minWaitedLSN = PG_UINT64_MAX;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		if (!pairingheap_is_empty(&waitLSNState->replayWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->replayWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedReplayLSN, minWaitedLSN);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		if (!pairingheap_is_empty(&waitLSNState->flushWaitersHeap))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->flushWaitersHeap);
+			WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+			minWaitedLSN = procInfo->waitLSN;
+		}
+		pg_atomic_write_u64(&waitLSNState->minWaitedFlushLSN, minWaitedLSN);
+	}
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	if (operation == WAIT_LSN_REPLAY)
+	{
+		Assert(!procInfo->inReplayHeap);
+		pairingheap_add(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else /* WAIT_LSN_FLUSH */
+	{
+		Assert(!procInfo->inFlushHeap);
+		pairingheap_add(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = true;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNOperation operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (operation == WAIT_LSN_REPLAY && procInfo->inReplayHeap)
+	{
+		pairingheap_remove(&waitLSNState->replayWaitersHeap, &procInfo->replayHeapNode);
+		procInfo->inReplayHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_REPLAY);
+	}
+	else if (operation == WAIT_LSN_FLUSH && procInfo->inFlushHeap)
+	{
+		pairingheap_remove(&waitLSNState->flushWaitersHeap, &procInfo->flushHeapNode);
+		procInfo->inFlushHeap = false;
+		updateMinWaitedLSN(WAIT_LSN_FLUSH);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNOperation operation, XLogRecPtr currentLSN)
+{
+	ProcNumber wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int numWakeUpProcs;
+	int i;
+	pairingheap *heap;
+
+	/* Select appropriate heap */
+	heap = (operation == WAIT_LSN_REPLAY) ?
+		   &waitLSNState->replayWaitersHeap :
+		   &waitLSNState->flushWaitersHeap;
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached.
+		 * Record process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(heap))
+		{
+			pairingheap_node *node = pairingheap_first(heap);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo = pairingheap_container(WaitLSNProcInfo, replayHeapNode, node);
+			else
+				procInfo = pairingheap_container(WaitLSNProcInfo, flushHeapNode, node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(heap);
+
+			/* Update appropriate flag */
+			if (operation == WAIT_LSN_REPLAY)
+				procInfo->inReplayHeap = false;
+			else
+				procInfo->inFlushHeap = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already reached.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for replay LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupReplay(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedReplayLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_REPLAY, currentLSN);
+}
+
+/*
+ * Wake up processes waiting for flush LSN to reach currentLSN
+ */
+void
+WaitLSNWakeupFlush(XLogRecPtr currentLSN)
+{
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedFlushLSN) > currentLSN)
+		return;
+
+	wakeupWaiters(WAIT_LSN_FLUSH, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only possible
+		 * to get a false positive.  But that will be eliminated by a recheck
+		 * inside deleteLSNWaiter().
+		 */
+		if (waitLSNState->procInfos[MyProcNumber].inReplayHeap)
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+		if (waitLSNState->procInfos[MyProcNumber].inFlushHeap)
+			deleteLSNWaiter(WAIT_LSN_FLUSH);
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is replayed, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was replayed.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN replayed.
+ */
+WaitLSNResult
+WaitForLSNReplay(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	/*
+	 * Add our process to the replay waiters heap.  It might happen that
+	 * target LSN gets replayed before we do.  Another check at the beginning
+	 * of the loop below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, WAIT_LSN_REPLAY);
+
+	for (;;)
+	{
+		int			rc;
+		currentLSN = GetXLogReplayRecPtr(NULL);
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * replayed.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been replayed */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_REPLAY);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN replay")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory replay heap.  We might
+	 * already be deleted by the startup process.  The 'inReplayHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_REPLAY);
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
+
+/*
+ * Wait until targetLSN has been flushed on a primary server.
+ * Returns only after the condition is satisfied or on FATAL exit.
+ */
+void
+WaitForLSNFlush(XLogRecPtr targetLSN)
+{
+	XLogRecPtr	currentLSN;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
+
+	/* We can only wait for flush when we are not in recovery */
+	Assert(!RecoveryInProgress());
+
+	/* Quick exit if already flushed */
+	currentLSN = GetFlushRecPtr(NULL);
+	if (targetLSN <= currentLSN)
+		return;
+
+	/* Add to flush waiters */
+	addLSNWaiter(targetLSN, WAIT_LSN_FLUSH);
+
+	/* Wait loop */
+	for (;;)
+	{
+		int			rc;
+
+		/* Check if the waited LSN has been flushed */
+		currentLSN = GetFlushRecPtr(NULL);
+		if (targetLSN <= currentLSN)
+			break;
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, -1,
+					   WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died. This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN flush")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory flush heap. We might
+	 * already be deleted by the waker process. The 'inFlushHeap' flag prevents
+	 * us from the double deletion.
+	 */
+	deleteLSNWaiter(WAIT_LSN_FLUSH);
+
+	return;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..ada2a460ca4
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+} WaitLSNResult;
+
+/*
+ * Wait operation types for LSN waiting facility.
+ */
+typedef enum WaitLSNOperation
+{
+	WAIT_LSN_REPLAY,	/* Waiting for replay on standby */
+	WAIT_LSN_FLUSH		/* Waiting for flush on primary */
+} WaitLSNOperation;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Type-safe heap membership flags */
+	bool		inReplayHeap;	/* In replay waiters heap */
+	bool		inFlushHeap;	/* In flush waiters heap */
+
+	/* Separate heap nodes for type safety */
+	pairingheap_node replayHeapNode;
+	pairingheap_node flushHeapNode;
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum replay LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedReplayLSN;
+
+	/*
+	 * A pairing heap of replay waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap replayWaitersHeap;
+
+	/*
+	 * The minimum flush LSN value some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after flushing
+	 * WAL.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedFlushLSN;
+
+	/*
+	 * A pairing heap of flush waiting processes ordered by LSN values (least LSN is
+	 * on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap flushWaitersHeap;
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeupReplay(XLogRecPtr currentLSN);
+extern void WaitLSNWakeupFlush(XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSNReplay(XLogRecPtr targetLSN);
+extern void WaitForLSNFlush(XLogRecPtr targetLSN);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-10-23 10:46                                                 ` Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-10-23 10:46 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; Álvaro Herrera <[email protected]>

Hi!

In Thu, Oct 16, 2025 at 10:12 AM Xuneng Zhou <[email protected]> wrote:
> On Wed, Oct 15, 2025 at 8:48 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > Thank you for the grammar review and the clear recommendation.
> >
> > On Wed, Oct 15, 2025 at 4:51 PM Álvaro Herrera <[email protected]> wrote:
> > >
> > > I didn't review the patch other than look at the grammar, but I disagree
> > > with using opt_with in it.  I think WITH should be a mandatory word, or
> > > just not be there at all.  The current formulation lets you do one of:
> > >
> > > 1. WAIT FOR LSN '123/456' WITH (opt = val);
> > > 2. WAIT FOR LSN '123/456' (opt = val);
> > > 3. WAIT FOR LSN '123/456';
> > >
> > > and I don't see why you need two ways to specify an option list.
> >
> > I agree with this as unnecessary choices are confusing.
> >
> > >
> > > So one option is to remove opt_wait_with_clause and just use
> > > opt_utility_option_list, which would remove the WITH keyword from there
> > > (ie. only keep 2 and 3 from the above list).  But I think that's worse:
> > > just look at the REPACK grammar[1], where we have to have additional
> > > productions for the optional parenthesized option list.
> > >
> > >
> > >
> > > So why not do just
> > >
> > > +opt_wait_with_clause:
> > > +           WITH '(' utility_option_list ')'        { $$ = $3; }
> > > +           | /*EMPTY*/                             { $$ = NIL; }
> > > +           ;
> > >
> > > which keeps options 1 and 3 of the list above.
> >
> > Your suggested approach of making WITH mandatory when options are
> > present looks better.
> > I've implemented the change as you recommended. Please see patch 3 in v16.
> >
> > >
> > >
> > >
> > > Note: you don't need to worry about WITH_LA, because that's only going
> > > to show up when the user writes WITH TIME or WITH ORDINALITY (see
> > > parser.c), and that's a syntax error anyway.
> > >
> >
> > Yeah, we require '(' immediately after WITH in our grammar, the
> > lookahead mechanism will keep it as regular WITH, and any attempt to
> > write "WITH TIME" or "WITH ORDINALITY" would be a syntax error anyway,
> > which is expected.
> >
>
> The filename of patch 1 is incorrect due to coping. Just correct it.

Thank you for rebasing the patch.

I've revised it.  The significant changes has been made to 0002, where
I reduced the code duplication.  Also, I run pgindent and pgperltidy
and made other small improvements.
Please, check.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v17-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch (3.1K, ../../CAPpHfdtKJqK_gDrqVGH-oq36UNp2FV2Gh0pYDHTCE0gDUjMKfw@mail.gmail.com/2-v17-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch)
  download | inline diff:
From 18a1a51c7f7a1bedb23169bbbe8974a9f803b82a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Oct 2025 10:29:05 +0800
Subject: [PATCH v17 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0003-Implement-WAIT-FOR-command.patch (44.6K, ../../CAPpHfdtKJqK_gDrqVGH-oq36UNp2FV2Gh0pYDHTCE0gDUjMKfw@mail.gmail.com/3-v17-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From a5db333b5b5b9e0c0c27f6f2bfbad8c4cf327f9b Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Thu, 23 Oct 2025 12:47:02 +0300
Subject: [PATCH v17 3/3] Implement WAIT FOR command
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Co-authored-by: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 +++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 302 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   1 +
 21 files changed, 931 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..b3fafb8b48c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ RESULT STATUS
+---------------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..3b8e842d1de
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..7c3a3541221 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6225,6 +6226,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..0e51148110f 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..67068a92dbf
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char	   *timeout_str;
+			const char *hintmsg;
+			double		result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round into
+			 * range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+						   defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
+	 * command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dc0c2886674..bec885ea73e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16453,6 +16454,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			WITH '(' utility_option_list ')'		{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17940,6 +17961,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18110,6 +18132,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18556,6 +18579,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18767,6 +18791,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 08791b8f75e..07b2e2fa67b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1163,10 +1163,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1743,7 +1744,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4e445fe0cd7..75c41ad0cb9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4384,4 +4384,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..9796a36a2f6
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,302 @@
+# Checks waiting for the lsn replay on standby using
+# WAIT FOR procedure.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres',
+	"WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# Test parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/, "get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/,
+	"get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - options without WITH keyword
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' (timeout '100ms');",
+	stderr => \$stderr);
+ok($stderr =~ /syntax error/,
+	"get syntax error when options specified without WITH keyword");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success", "WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout",
+	"WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok( $stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 5. Also, check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 6. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 38d346a3691..d92cb2e6a71 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3269,6 +3269,7 @@ WaitLSNState
 WaitLSNProcInfo
 WaitLSNResult
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (21.5K, ../../CAPpHfdtKJqK_gDrqVGH-oq36UNp2FV2Gh0pYDHTCE0gDUjMKfw@mail.gmail.com/4-v17-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 2d3e55c71e69e3cf39be10e42a57ad03ebc28217 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Thu, 23 Oct 2025 11:58:17 +0300
Subject: [PATCH v17 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 409 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 |  98 +++++
 src/include/storage/lwlocklist.h              |   1 +
 src/tools/pgindent/typedefs.list              |   4 +
 8 files changed, 521 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..8276c2f0947
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,409 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch before it wakens up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		int			i;
+
+		/* Initialize heaps and tracking */
+		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
+		{
+			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
+			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, (void *) (uintptr_t) i);
+		}
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for LSN waiters heaps. Waiting processes are ordered by
+ * LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	int			i = (uintptr_t) arg;
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNType operation)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+		minWaitedLSN = procInfo->waitLSN;
+	}
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNType operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	Assert(!procInfo->inHeap[i]);
+	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+	procInfo->inHeap[i] = true;
+	updateMinWaitedLSN(operation);
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNType operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (procInfo->inHeap[i])
+	{
+		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+		procInfo->inHeap[i] = false;
+		updateMinWaitedLSN(operation);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNType operation, XLogRecPtr currentLSN)
+{
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached. Record
+		 * process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap[i]);
+
+			/* Update appropriate flag */
+			procInfo->inHeap[i] = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes, whose waited LSNs are already reached.
+		 * As the time consuming operations, we do this outside of
+		 * WaitLSNLock. This is  actually fine because procLatch isn't ever
+		 * freed, so we just can potentially set the wrong process' (or no
+		 * process') latch.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for LSN to reach currentLSN
+ */
+void
+WaitLSNWakeup(WaitLSNType operation, XLogRecPtr currentLSN)
+{
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		return;
+
+	wakeupWaiters(operation, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		int			i;
+
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only
+		 * possible to get a false positive.  But that will be eliminated by a
+		 * recheck inside deleteLSNWaiter().
+		 */
+
+		for (i = 0; i < (int) WAIT_LSN_TYPE_COUNT; i++)
+		{
+			if (waitLSNState->procInfos[MyProcNumber].inHeap[i])
+				deleteLSNWaiter((WaitLSNType) i);
+		}
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is reached, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was reached.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN reached.
+ */
+WaitLSNResult
+WaitForLSN(WaitLSNType operation, XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the waiters heap.  It might happen that target LSN
+	 * gets reached before we do.  Another check at the beginning of the loop
+	 * below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, operation);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = -1;
+
+		if (operation == WAIT_LSN_TYPE_REPLAY)
+			currentLSN = GetXLogReplayRecPtr(NULL);
+		else
+			currentLSN = GetFlushRecPtr(NULL);
+
+		/* Recheck that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but recheck if target LSN was already
+			 * reached.  See the comment regarding deleteLSNWaiter() below.
+			 */
+			deleteLSNWaiter(operation);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been reached */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   (operation == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN")));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory heap.  We might already be
+	 * deleted by the startup process.  The 'inHeap' flags prevents us from
+	 * the double deletion.
+	 */
+	deleteLSNWaiter(operation);
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..d7aad6d8be4
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT		/* Timeout occurred */
+} WaitLSNResult;
+
+/*
+ * LSN type for waiting facility.
+ */
+typedef enum WaitLSNType
+{
+	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
+	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
+	WAIT_LSN_TYPE_COUNT = 2
+} WaitLSNType;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Heap membership flags for LSN types */
+	bool		inHeap[WAIT_LSN_TYPE_COUNT];
+
+	/* Heap nodes for LSN types */
+	pairingheap_node heapNode[WAIT_LSN_TYPE_COUNT];
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN values some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * A pairing heaps of waiting processes ordered by LSN values (least LSN
+	 * is on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(WaitLSNType operation, XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSN(WaitLSNType operation, XLogRecPtr targetLSN,
+								int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 377a7946585..38d346a3691 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3264,6 +3264,10 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNType
+WaitLSNState
+WaitLSNProcInfo
+WaitLSNResult
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-10-23 12:58                                                   ` Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-10-23 12:58 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; Álvaro Herrera <[email protected]>

Hi,

On Thu, Oct 23, 2025 at 6:46 PM Alexander Korotkov <[email protected]> wrote:
>
> Hi!
>
> In Thu, Oct 16, 2025 at 10:12 AM Xuneng Zhou <[email protected]> wrote:
> > On Wed, Oct 15, 2025 at 8:48 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > Thank you for the grammar review and the clear recommendation.
> > >
> > > On Wed, Oct 15, 2025 at 4:51 PM Álvaro Herrera <[email protected]> wrote:
> > > >
> > > > I didn't review the patch other than look at the grammar, but I disagree
> > > > with using opt_with in it.  I think WITH should be a mandatory word, or
> > > > just not be there at all.  The current formulation lets you do one of:
> > > >
> > > > 1. WAIT FOR LSN '123/456' WITH (opt = val);
> > > > 2. WAIT FOR LSN '123/456' (opt = val);
> > > > 3. WAIT FOR LSN '123/456';
> > > >
> > > > and I don't see why you need two ways to specify an option list.
> > >
> > > I agree with this as unnecessary choices are confusing.
> > >
> > > >
> > > > So one option is to remove opt_wait_with_clause and just use
> > > > opt_utility_option_list, which would remove the WITH keyword from there
> > > > (ie. only keep 2 and 3 from the above list).  But I think that's worse:
> > > > just look at the REPACK grammar[1], where we have to have additional
> > > > productions for the optional parenthesized option list.
> > > >
> > > >
> > > >
> > > > So why not do just
> > > >
> > > > +opt_wait_with_clause:
> > > > +           WITH '(' utility_option_list ')'        { $$ = $3; }
> > > > +           | /*EMPTY*/                             { $$ = NIL; }
> > > > +           ;
> > > >
> > > > which keeps options 1 and 3 of the list above.
> > >
> > > Your suggested approach of making WITH mandatory when options are
> > > present looks better.
> > > I've implemented the change as you recommended. Please see patch 3 in v16.
> > >
> > > >
> > > >
> > > >
> > > > Note: you don't need to worry about WITH_LA, because that's only going
> > > > to show up when the user writes WITH TIME or WITH ORDINALITY (see
> > > > parser.c), and that's a syntax error anyway.
> > > >
> > >
> > > Yeah, we require '(' immediately after WITH in our grammar, the
> > > lookahead mechanism will keep it as regular WITH, and any attempt to
> > > write "WITH TIME" or "WITH ORDINALITY" would be a syntax error anyway,
> > > which is expected.
> > >
> >
> > The filename of patch 1 is incorrect due to coping. Just correct it.
>
> Thank you for rebasing the patch.
>
> I've revised it.  The significant changes has been made to 0002, where
> I reduced the code duplication.  Also, I run pgindent and pgperltidy
> and made other small improvements.
> Please, check.

Thanks for updating the patch set!
Patch 2 looks more elegant after the revision. I’ll review them soon.

Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-02 06:24                                                     ` Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-02 06:24 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; Álvaro Herrera <[email protected]>

Hi, Alexander!

On Thu, Oct 23, 2025 at 8:58 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Thu, Oct 23, 2025 at 6:46 PM Alexander Korotkov <[email protected]> wrote:
> >
> > Hi!
> >
> > In Thu, Oct 16, 2025 at 10:12 AM Xuneng Zhou <[email protected]> wrote:
> > > On Wed, Oct 15, 2025 at 8:48 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi,
> > > >
> > > > Thank you for the grammar review and the clear recommendation.
> > > >
> > > > On Wed, Oct 15, 2025 at 4:51 PM Álvaro Herrera <[email protected]> wrote:
> > > > >
> > > > > I didn't review the patch other than look at the grammar, but I disagree
> > > > > with using opt_with in it.  I think WITH should be a mandatory word, or
> > > > > just not be there at all.  The current formulation lets you do one of:
> > > > >
> > > > > 1. WAIT FOR LSN '123/456' WITH (opt = val);
> > > > > 2. WAIT FOR LSN '123/456' (opt = val);
> > > > > 3. WAIT FOR LSN '123/456';
> > > > >
> > > > > and I don't see why you need two ways to specify an option list.
> > > >
> > > > I agree with this as unnecessary choices are confusing.
> > > >
> > > > >
> > > > > So one option is to remove opt_wait_with_clause and just use
> > > > > opt_utility_option_list, which would remove the WITH keyword from there
> > > > > (ie. only keep 2 and 3 from the above list).  But I think that's worse:
> > > > > just look at the REPACK grammar[1], where we have to have additional
> > > > > productions for the optional parenthesized option list.
> > > > >
> > > > >
> > > > >
> > > > > So why not do just
> > > > >
> > > > > +opt_wait_with_clause:
> > > > > +           WITH '(' utility_option_list ')'        { $$ = $3; }
> > > > > +           | /*EMPTY*/                             { $$ = NIL; }
> > > > > +           ;
> > > > >
> > > > > which keeps options 1 and 3 of the list above.
> > > >
> > > > Your suggested approach of making WITH mandatory when options are
> > > > present looks better.
> > > > I've implemented the change as you recommended. Please see patch 3 in v16.
> > > >
> > > > >
> > > > >
> > > > >
> > > > > Note: you don't need to worry about WITH_LA, because that's only going
> > > > > to show up when the user writes WITH TIME or WITH ORDINALITY (see
> > > > > parser.c), and that's a syntax error anyway.
> > > > >
> > > >
> > > > Yeah, we require '(' immediately after WITH in our grammar, the
> > > > lookahead mechanism will keep it as regular WITH, and any attempt to
> > > > write "WITH TIME" or "WITH ORDINALITY" would be a syntax error anyway,
> > > > which is expected.
> > > >
> > >
> > > The filename of patch 1 is incorrect due to coping. Just correct it.
> >
> > Thank you for rebasing the patch.
> >
> > I've revised it.  The significant changes has been made to 0002, where
> > I reduced the code duplication.  Also, I run pgindent and pgperltidy
> > and made other small improvements.
> > Please, check.
>
> Thanks for updating the patch set!
> Patch 2 looks more elegant after the revision. I’ll review them soon.

I’ve made a few minor updates to the comments and docs in patches 2
and 3. The patch set LGTM now.

Best,
Xuneng


Attachments:

  [application/octet-stream] v18-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch (3.0K, ../../CABPTF7W2E4N+EmoQ9X4WBkJ8U=ZFOeu_NWcwMTge3oPomqS86Q@mail.gmail.com/2-v18-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch)
  download | inline diff:
From b0ee110622dacd2d4769da6915580e9c3220c09f Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sun, 2 Nov 2025 11:56:53 +0800
Subject: [PATCH v18 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



  [application/octet-stream] v18-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (21.5K, ../../CABPTF7W2E4N+EmoQ9X4WBkJ8U=ZFOeu_NWcwMTge3oPomqS86Q@mail.gmail.com/3-v18-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From b611a90989aec7695349e47fd1fb89d7dd9b1872 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sun, 2 Nov 2025 11:59:42 +0800
Subject: [PATCH v18 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 409 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 |  98 +++++
 src/include/storage/lwlocklist.h              |   1 +
 src/tools/pgindent/typedefs.list              |   5 +
 8 files changed, 522 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..1f4b38a5114
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,409 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch until it is woken up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		int			i;
+
+		/* Initialize heaps and tracking */
+		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
+		{
+			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
+			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, (void *) (uintptr_t) i);
+		}
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for LSN waiters heaps. Waiting processes are ordered by
+ * LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	int			i = (uintptr_t) arg;
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNType operation)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+		minWaitedLSN = procInfo->waitLSN;
+	}
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNType operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	Assert(!procInfo->inHeap[i]);
+	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+	procInfo->inHeap[i] = true;
+	updateMinWaitedLSN(operation);
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNType operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (procInfo->inHeap[i])
+	{
+		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+		procInfo->inHeap[i] = false;
+		updateMinWaitedLSN(operation);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNType operation, XLogRecPtr currentLSN)
+{
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached. Record
+		 * process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap[i]);
+
+			/* Update appropriate flag */
+			procInfo->inHeap[i] = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes whose waited LSNs have been reached.
+		 * Since SetLatch() is a time-consuming operation, we do this outside
+		 * of WaitLSNLock. This is safe because procLatch is never freed, so
+		 * at worst we may set a latch for the wrong process or for no process
+		 * at all, which is harmless.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for LSN to reach currentLSN
+ */
+void
+WaitLSNWakeup(WaitLSNType operation, XLogRecPtr currentLSN)
+{
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		return;
+
+	wakeupWaiters(operation, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		int			i;
+
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only
+		 * possible to get a false positive.  But that will be eliminated by a
+		 * recheck inside deleteLSNWaiter().
+		 */
+
+		for (i = 0; i < (int) WAIT_LSN_TYPE_COUNT; i++)
+		{
+			if (waitLSNState->procInfos[MyProcNumber].inHeap[i])
+				deleteLSNWaiter((WaitLSNType) i);
+		}
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is reached, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was reached.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN reached.
+ */
+WaitLSNResult
+WaitForLSN(WaitLSNType operation, XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the waiters heap.  It might happen that target LSN
+	 * gets reached before we do.  The check at the beginning of the loop
+	 * below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, operation);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = -1;
+
+		if (operation == WAIT_LSN_TYPE_REPLAY)
+			currentLSN = GetXLogReplayRecPtr(NULL);
+		else
+			currentLSN = GetFlushRecPtr(NULL);
+
+		/* Check that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but check if target LSN was already
+			 * reached.
+			 */
+			deleteLSNWaiter(operation);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been reached */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   (operation == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					 errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN"));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory heap.  We might already be
+	 * deleted by the startup process.  The 'inHeap' flags prevents us from
+	 * the double deletion.
+	 */
+	deleteLSNWaiter(operation);
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..d7aad6d8be4
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT		/* Timeout occurred */
+} WaitLSNResult;
+
+/*
+ * LSN type for waiting facility.
+ */
+typedef enum WaitLSNType
+{
+	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
+	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
+	WAIT_LSN_TYPE_COUNT = 2
+} WaitLSNType;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Heap membership flags for LSN types */
+	bool		inHeap[WAIT_LSN_TYPE_COUNT];
+
+	/* Heap nodes for LSN types */
+	pairingheap_node heapNode[WAIT_LSN_TYPE_COUNT];
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN values some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * A pairing heaps of waiting processes ordered by LSN values (least LSN
+	 * is on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(WaitLSNType operation, XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSN(WaitLSNType operation, XLogRecPtr targetLSN,
+								int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 018b5919cf6..e34dcf97df8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3265,7 +3265,12 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNType
+WaitLSNState
+WaitLSNProcInfo
+WaitLSNResult
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.51.0



  [application/octet-stream] v18-0003-Implement-WAIT-FOR-command.patch (44.3K, ../../CABPTF7W2E4N+EmoQ9X4WBkJ8U=ZFOeu_NWcwMTge3oPomqS86Q@mail.gmail.com/4-v18-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From f009c6a8bd305b50889366877cc7a8581fb40157 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sun, 2 Nov 2025 12:03:13 +0800
Subject: [PATCH v18 3/3] Implement WAIT FOR command
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 +++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 302 ++++++++++++++++++++++
 20 files changed, 930 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..742deb037b7 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..3b8e842d1de
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fd91bcd68ec..45a16bd1ec2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6227,6 +6228,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..0e51148110f 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..67068a92dbf
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char	   *timeout_str;
+			const char *hintmsg;
+			double		result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round into
+			 * range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+						   defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
+	 * command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4b29c822e8..a4e6f80504b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16462,6 +16463,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			WITH '(' utility_option_list ')'		{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17949,6 +17970,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18119,6 +18141,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18565,6 +18588,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18776,6 +18800,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 74179139fa9..fde78c55160 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1158,10 +1158,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1738,7 +1739,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecbddd12e1b..d14294a4ece 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,4 +4385,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..e0ddb06a2f0
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,302 @@
+# Checks waiting for the LSN replay on standby using
+# the WAIT FOR command.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres',
+	"WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Check parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/, "get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/,
+	"get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - options without WITH keyword
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' (timeout '100ms');",
+	stderr => \$stderr);
+ok($stderr =~ /syntax error/,
+	"get syntax error when options specified without WITH keyword");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success", "WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout",
+	"WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok( $stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 7. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-03 02:20                                                       ` Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-03 02:20 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; Álvaro Herrera <[email protected]>

Hi,

On Sun, Nov 2, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi, Alexander!
>
> On Thu, Oct 23, 2025 at 8:58 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Thu, Oct 23, 2025 at 6:46 PM Alexander Korotkov <[email protected]> wrote:
> > >
> > > Hi!
> > >
> > > In Thu, Oct 16, 2025 at 10:12 AM Xuneng Zhou <[email protected]> wrote:
> > > > On Wed, Oct 15, 2025 at 8:48 PM Xuneng Zhou <[email protected]> wrote:
> > > > >
> > > > > Hi,
> > > > >
> > > > > Thank you for the grammar review and the clear recommendation.
> > > > >
> > > > > On Wed, Oct 15, 2025 at 4:51 PM Álvaro Herrera <[email protected]> wrote:
> > > > > >
> > > > > > I didn't review the patch other than look at the grammar, but I disagree
> > > > > > with using opt_with in it.  I think WITH should be a mandatory word, or
> > > > > > just not be there at all.  The current formulation lets you do one of:
> > > > > >
> > > > > > 1. WAIT FOR LSN '123/456' WITH (opt = val);
> > > > > > 2. WAIT FOR LSN '123/456' (opt = val);
> > > > > > 3. WAIT FOR LSN '123/456';
> > > > > >
> > > > > > and I don't see why you need two ways to specify an option list.
> > > > >
> > > > > I agree with this as unnecessary choices are confusing.
> > > > >
> > > > > >
> > > > > > So one option is to remove opt_wait_with_clause and just use
> > > > > > opt_utility_option_list, which would remove the WITH keyword from there
> > > > > > (ie. only keep 2 and 3 from the above list).  But I think that's worse:
> > > > > > just look at the REPACK grammar[1], where we have to have additional
> > > > > > productions for the optional parenthesized option list.
> > > > > >
> > > > > >
> > > > > >
> > > > > > So why not do just
> > > > > >
> > > > > > +opt_wait_with_clause:
> > > > > > +           WITH '(' utility_option_list ')'        { $$ = $3; }
> > > > > > +           | /*EMPTY*/                             { $$ = NIL; }
> > > > > > +           ;
> > > > > >
> > > > > > which keeps options 1 and 3 of the list above.
> > > > >
> > > > > Your suggested approach of making WITH mandatory when options are
> > > > > present looks better.
> > > > > I've implemented the change as you recommended. Please see patch 3 in v16.
> > > > >
> > > > > >
> > > > > >
> > > > > >
> > > > > > Note: you don't need to worry about WITH_LA, because that's only going
> > > > > > to show up when the user writes WITH TIME or WITH ORDINALITY (see
> > > > > > parser.c), and that's a syntax error anyway.
> > > > > >
> > > > >
> > > > > Yeah, we require '(' immediately after WITH in our grammar, the
> > > > > lookahead mechanism will keep it as regular WITH, and any attempt to
> > > > > write "WITH TIME" or "WITH ORDINALITY" would be a syntax error anyway,
> > > > > which is expected.
> > > > >
> > > >
> > > > The filename of patch 1 is incorrect due to coping. Just correct it.
> > >
> > > Thank you for rebasing the patch.
> > >
> > > I've revised it.  The significant changes has been made to 0002, where
> > > I reduced the code duplication.  Also, I run pgindent and pgperltidy
> > > and made other small improvements.
> > > Please, check.
> >
> > Thanks for updating the patch set!
> > Patch 2 looks more elegant after the revision. I’ll review them soon.
>
> I’ve made a few minor updates to the comments and docs in patches 2
> and 3. The patch set LGTM now.

Fix an minor issue in v18: WaitStmt was mistakenly added to
pgindent/typedefs.list in patch 2, but it should belong to patch 3.

Best,
Xuneng


Attachments:

  [application/octet-stream] v19-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (21.5K, ../../CABPTF7UUfnRpraEfNVui=acAhUZ9dGpR=nHxQYjdS+XTTFvgyw@mail.gmail.com/2-v19-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 65d6c1d497389925961738207422cd2bc69c95bd Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sun, 2 Nov 2025 11:59:42 +0800
Subject: [PATCH v19 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 409 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 |  98 +++++
 src/include/storage/lwlocklist.h              |   1 +
 src/tools/pgindent/typedefs.list              |   4 +
 8 files changed, 521 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..1f4b38a5114
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,409 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch until it is woken up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		int			i;
+
+		/* Initialize heaps and tracking */
+		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
+		{
+			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
+			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, (void *) (uintptr_t) i);
+		}
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for LSN waiters heaps. Waiting processes are ordered by
+ * LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	int			i = (uintptr_t) arg;
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified operation type
+ */
+static void
+updateMinWaitedLSN(WaitLSNType operation)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+		minWaitedLSN = procInfo->waitLSN;
+	}
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+}
+
+/*
+ * Add current process to appropriate waiters heap based on operation type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNType operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	Assert(!procInfo->inHeap[i]);
+	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+	procInfo->inHeap[i] = true;
+	updateMinWaitedLSN(operation);
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on operation type
+ */
+static void
+deleteLSNWaiter(WaitLSNType operation)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (procInfo->inHeap[i])
+	{
+		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+		procInfo->inHeap[i] = false;
+		updateMinWaitedLSN(operation);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNType operation, XLogRecPtr currentLSN)
+{
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached. Record
+		 * process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap[i]);
+
+			/* Update appropriate flag */
+			procInfo->inHeap[i] = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(operation);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes whose waited LSNs have been reached.
+		 * Since SetLatch() is a time-consuming operation, we do this outside
+		 * of WaitLSNLock. This is safe because procLatch is never freed, so
+		 * at worst we may set a latch for the wrong process or for no process
+		 * at all, which is harmless.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for LSN to reach currentLSN
+ */
+void
+WaitLSNWakeup(WaitLSNType operation, XLogRecPtr currentLSN)
+{
+	int			i = (int) operation;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		return;
+
+	wakeupWaiters(operation, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		int			i;
+
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only
+		 * possible to get a false positive.  But that will be eliminated by a
+		 * recheck inside deleteLSNWaiter().
+		 */
+
+		for (i = 0; i < (int) WAIT_LSN_TYPE_COUNT; i++)
+		{
+			if (waitLSNState->procInfos[MyProcNumber].inHeap[i])
+				deleteLSNWaiter((WaitLSNType) i);
+		}
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is reached, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was reached.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN reached.
+ */
+WaitLSNResult
+WaitForLSN(WaitLSNType operation, XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the waiters heap.  It might happen that target LSN
+	 * gets reached before we do.  The check at the beginning of the loop
+	 * below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, operation);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = -1;
+
+		if (operation == WAIT_LSN_TYPE_REPLAY)
+			currentLSN = GetXLogReplayRecPtr(NULL);
+		else
+			currentLSN = GetFlushRecPtr(NULL);
+
+		/* Check that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but check if target LSN was already
+			 * reached.
+			 */
+			deleteLSNWaiter(operation);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been reached */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   (operation == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					 errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to unexpected postmaster exit"),
+					 errcontext("while waiting for LSN"));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory heap.  We might already be
+	 * deleted by the startup process.  The 'inHeap' flags prevents us from
+	 * the double deletion.
+	 */
+	deleteLSNWaiter(operation);
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..d7aad6d8be4
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT		/* Timeout occurred */
+} WaitLSNResult;
+
+/*
+ * LSN type for waiting facility.
+ */
+typedef enum WaitLSNType
+{
+	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
+	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
+	WAIT_LSN_TYPE_COUNT = 2
+} WaitLSNType;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Heap membership flags for LSN types */
+	bool		inHeap[WAIT_LSN_TYPE_COUNT];
+
+	/* Heap nodes for LSN types */
+	pairingheap_node heapNode[WAIT_LSN_TYPE_COUNT];
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN values some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * A pairing heaps of waiting processes ordered by LSN values (least LSN
+	 * is on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(WaitLSNType operation, XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSN(WaitLSNType operation, XLogRecPtr targetLSN,
+								int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 018b5919cf6..237d33c538c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3265,6 +3265,10 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNType
+WaitLSNState
+WaitLSNProcInfo
+WaitLSNResult
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.51.0



  [application/octet-stream] v19-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch (3.0K, ../../CABPTF7UUfnRpraEfNVui=acAhUZ9dGpR=nHxQYjdS+XTTFvgyw@mail.gmail.com/3-v19-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch)
  download | inline diff:
From b0ee110622dacd2d4769da6915580e9c3220c09f Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sun, 2 Nov 2025 11:56:53 +0800
Subject: [PATCH v19 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.51.0



  [application/octet-stream] v19-0003-Implement-WAIT-FOR-command.patch (44.6K, ../../CABPTF7UUfnRpraEfNVui=acAhUZ9dGpR=nHxQYjdS+XTTFvgyw@mail.gmail.com/4-v19-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From b686e6126ac9eb5b54a2782ac8be3539454da49a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Mon, 3 Nov 2025 09:57:30 +0800
Subject: [PATCH v19 3/3] Implement WAIT FOR command
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 +++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 302 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   1 +
 21 files changed, 931 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..742deb037b7 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..3b8e842d1de
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fd91bcd68ec..45a16bd1ec2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6227,6 +6228,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..0e51148110f 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..67068a92dbf
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char	   *timeout_str;
+			const char *hintmsg;
+			double		result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round into
+			 * range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+						   defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
+	 * command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4b29c822e8..a4e6f80504b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16462,6 +16463,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			WITH '(' utility_option_list ')'		{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17949,6 +17970,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18119,6 +18141,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18565,6 +18588,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18776,6 +18800,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 74179139fa9..fde78c55160 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1158,10 +1158,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1738,7 +1739,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecbddd12e1b..d14294a4ece 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,4 +4385,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..e0ddb06a2f0
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,302 @@
+# Checks waiting for the LSN replay on standby using
+# the WAIT FOR command.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres',
+	"WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Check parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/, "get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/,
+	"get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - options without WITH keyword
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' (timeout '100ms');",
+	stderr => \$stderr);
+ok($stderr =~ /syntax error/,
+	"get syntax error when options specified without WITH keyword");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success", "WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout",
+	"WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok( $stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 7. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 237d33c538c..e34dcf97df8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3270,6 +3270,7 @@ WaitLSNState
 WaitLSNProcInfo
 WaitLSNResult
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-03 11:46                                                         ` Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-03 11:46 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; Álvaro Herrera <[email protected]>

Hello, Xuneng!

On Mon, Nov 3, 2025 at 4:20 AM Xuneng Zhou <[email protected]> wrote:
> On Sun, Nov 2, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> > On Thu, Oct 23, 2025 at 8:58 PM Xuneng Zhou <[email protected]> wrote:
> > I’ve made a few minor updates to the comments and docs in patches 2
> > and 3. The patch set LGTM now.
>
> Fix an minor issue in v18: WaitStmt was mistakenly added to
> pgindent/typedefs.list in patch 2, but it should belong to patch 3.

Thank you.  I also made some minor changes to 0002 renaming
"operation" => "lsnType".

I'd like to give this subject another chance for pg19.  I'm going to
push this if no objections.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v20-0002-Add-infrastructure-for-efficient-LSN-waiting.patch (21.5K, ../../CAPpHfdsAsahNw7LQ1Yb6wN04RV2GGQ9XFiHnP_BDGZF=Yiickg@mail.gmail.com/2-v20-0002-Add-infrastructure-for-efficient-LSN-waiting.patch)
  download | inline diff:
From 27d57234c169c6612e432bb5ff19acac2c5982d9 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 3 Nov 2025 13:31:13 +0200
Subject: [PATCH v20 2/3] Add infrastructure for efficient LSN waiting

Implement a new facility that allows processes to wait for WAL to reach
specific LSNs, both on primary (waiting for flush) and standby (waiting
for replay) servers.

The implementation uses shared memory with per-backend information
organized into pairing heaps, allowing O(1) access to the minimum
waited LSN. This enables fast-path checks: after replaying or flushing
WAL, the startup process or WAL writer can quickly determine if any
waiters need to be awakened.

Key components:
- New xlogwait.c/h module with WaitForLSNReplay() and WaitForLSNFlush()
- Separate pairing heaps for replay and flush waiters
- WaitLSN lightweight lock for coordinating shared state
- Wait events WAIT_FOR_WAL_REPLAY and WAIT_FOR_WAL_FLUSH for monitoring

This infrastructure can be used by features that need to wait for WAL
operations to complete.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/Makefile           |   3 +-
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlogwait.c         | 409 ++++++++++++++++++
 src/backend/storage/ipc/ipci.c                |   3 +
 .../utils/activity/wait_event_names.txt       |   3 +
 src/include/access/xlogwait.h                 |  98 +++++
 src/include/storage/lwlocklist.h              |   1 +
 src/tools/pgindent/typedefs.list              |   4 +
 8 files changed, 521 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/transam/xlogwait.c
 create mode 100644 src/include/access/xlogwait.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..a32f473e0a2 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	xlogreader.o \
 	xlogrecovery.o \
 	xlogstats.o \
-	xlogutils.o
+	xlogutils.o \
+	xlogwait.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..74a62ab3eab 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -24,6 +24,7 @@ backend_sources += files(
   'xlogrecovery.c',
   'xlogstats.c',
   'xlogutils.c',
+  'xlogwait.c',
 )
 
 # used by frontend programs to build a frontend xlogreader
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
new file mode 100644
index 00000000000..e04567cfd67
--- /dev/null
+++ b/src/backend/access/transam/xlogwait.c
@@ -0,0 +1,409 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.c
+ *	  Implements waiting for WAL operations to reach specific LSNs.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xlogwait.c
+ *
+ * NOTES
+ *		This file implements waiting for WAL operations to reach specific LSNs
+ *		on both physical standby and primary servers. The core idea is simple:
+ *		every process that wants to wait publishes the LSN it needs to the
+ *		shared memory, and the appropriate process (startup on standby, or
+ *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *
+ *		The shared memory used by this module comprises a procInfos
+ *		per-backend array with the information of the awaited LSN for each
+ *		of the backend processes.  The elements of that array are organized
+ *		into a pairing heap waitersHeap, which allows for very fast finding
+ *		of the least awaited LSN.
+ *
+ *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
+ *		waiter process publishes information about itself to the shared
+ *		memory and waits on the latch until it is woken up by the appropriate
+ *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
+ *		information about itself in the shared memory.
+ *
+ *		On standby servers: After replaying a WAL record, the startup process
+ *		first performs a fast path check minWaitedLSN > replayLSN.  If this
+ *		check is negative, it checks waitersHeap and wakes up the backend
+ *		whose awaited LSNs are reached.
+ *
+ *		On primary servers: After flushing WAL, the WAL writer or backend
+ *		process performs a similar check against the flush LSN and wakes up
+ *		waiters whose target flush LSNs have been reached.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <math.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+#include "utils/fmgrprotos.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
+						void *arg);
+
+struct WaitLSNState *waitLSNState = NULL;
+
+/* Report the amount of shared memory space needed for WaitLSNState. */
+Size
+WaitLSNShmemSize(void)
+{
+	Size		size;
+
+	size = offsetof(WaitLSNState, procInfos);
+	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
+	return size;
+}
+
+/* Initialize the WaitLSNState in the shared memory. */
+void
+WaitLSNShmemInit(void)
+{
+	bool		found;
+
+	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
+													WaitLSNShmemSize(),
+													&found);
+	if (!found)
+	{
+		int			i;
+
+		/* Initialize heaps and tracking */
+		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
+		{
+			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
+			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, (void *) (uintptr_t) i);
+		}
+
+		/* Initialize process info array */
+		memset(&waitLSNState->procInfos, 0,
+			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+	}
+}
+
+/*
+ * Comparison function for LSN waiters heaps. Waiting processes are ordered by
+ * LSN, so that the waiter with smallest LSN is at the top.
+ */
+static int
+waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
+{
+	int			i = (uintptr_t) arg;
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], b);
+
+	if (aproc->waitLSN < bproc->waitLSN)
+		return 1;
+	else if (aproc->waitLSN > bproc->waitLSN)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Update minimum waited LSN for the specified LSN type
+ */
+static void
+updateMinWaitedLSN(WaitLSNType lsnType)
+{
+	XLogRecPtr	minWaitedLSN = PG_UINT64_MAX;
+	int			i = (int) lsnType;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	if (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+	{
+		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+		minWaitedLSN = procInfo->waitLSN;
+	}
+	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+}
+
+/*
+ * Add current process to appropriate waiters heap based on LSN type
+ */
+static void
+addLSNWaiter(XLogRecPtr lsn, WaitLSNType lsnType)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) lsnType;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	procInfo->procno = MyProcNumber;
+	procInfo->waitLSN = lsn;
+
+	Assert(!procInfo->inHeap[i]);
+	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+	procInfo->inHeap[i] = true;
+	updateMinWaitedLSN(lsnType);
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Remove current process from appropriate waiters heap based on LSN type
+ */
+static void
+deleteLSNWaiter(WaitLSNType lsnType)
+{
+	WaitLSNProcInfo *procInfo = &waitLSNState->procInfos[MyProcNumber];
+	int			i = (int) lsnType;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+	if (procInfo->inHeap[i])
+	{
+		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
+		procInfo->inHeap[i] = false;
+		updateMinWaitedLSN(lsnType);
+	}
+
+	LWLockRelease(WaitLSNLock);
+}
+
+/*
+ * Size of a static array of procs to wakeup by WaitLSNWakeup() allocated
+ * on the stack.  It should be enough to take single iteration for most cases.
+ */
+#define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
+
+/*
+ * Remove waiters whose LSN has been reached from the heap and set their
+ * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
+ * and set latches for all waiters.
+ *
+ * This function first accumulates waiters to wake up into an array, then
+ * wakes them up without holding a WaitLSNLock.  The array size is static and
+ * equal to WAKEUP_PROC_STATIC_ARRAY_SIZE.  That should be more than enough
+ * to wake up all the waiters at once in the vast majority of cases.  However,
+ * if there are more waiters, this function will loop to process them in
+ * multiple chunks.
+ */
+static void
+wakeupWaiters(WaitLSNType lsnType, XLogRecPtr currentLSN)
+{
+	ProcNumber	wakeUpProcs[WAKEUP_PROC_STATIC_ARRAY_SIZE];
+	int			numWakeUpProcs;
+	int			i = (int) lsnType;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	do
+	{
+		numWakeUpProcs = 0;
+		LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
+
+		/*
+		 * Iterate the waiters heap until we find LSN not yet reached. Record
+		 * process numbers to wake up, but send wakeups after releasing lock.
+		 */
+		while (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
+		{
+			pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
+			WaitLSNProcInfo *procInfo;
+
+			/* Get procInfo using appropriate heap node */
+			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+
+			if (!XLogRecPtrIsInvalid(currentLSN) && procInfo->waitLSN > currentLSN)
+				break;
+
+			Assert(numWakeUpProcs < WAKEUP_PROC_STATIC_ARRAY_SIZE);
+			wakeUpProcs[numWakeUpProcs++] = procInfo->procno;
+			(void) pairingheap_remove_first(&waitLSNState->waitersHeap[i]);
+
+			/* Update appropriate flag */
+			procInfo->inHeap[i] = false;
+
+			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
+				break;
+		}
+
+		updateMinWaitedLSN(lsnType);
+		LWLockRelease(WaitLSNLock);
+
+		/*
+		 * Set latches for processes whose waited LSNs have been reached.
+		 * Since SetLatch() is a time-consuming operation, we do this outside
+		 * of WaitLSNLock. This is safe because procLatch is never freed, so
+		 * at worst we may set a latch for the wrong process or for no process
+		 * at all, which is harmless.
+		 */
+		for (i = 0; i < numWakeUpProcs; i++)
+			SetLatch(&GetPGProcByNumber(wakeUpProcs[i])->procLatch);
+
+	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
+}
+
+/*
+ * Wake up processes waiting for LSN to reach currentLSN
+ */
+void
+WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
+{
+	int			i = (int) lsnType;
+
+	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
+
+	/* Fast path check */
+	if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		return;
+
+	wakeupWaiters(lsnType, currentLSN);
+}
+
+/*
+ * Clean up LSN waiters for exiting process
+ */
+void
+WaitLSNCleanup(void)
+{
+	if (waitLSNState)
+	{
+		int			i;
+
+		/*
+		 * We do a fast-path check of the heap flags without the lock.  These
+		 * flags are set to true only by the process itself.  So, it's only
+		 * possible to get a false positive.  But that will be eliminated by a
+		 * recheck inside deleteLSNWaiter().
+		 */
+
+		for (i = 0; i < (int) WAIT_LSN_TYPE_COUNT; i++)
+		{
+			if (waitLSNState->procInfos[MyProcNumber].inHeap[i])
+				deleteLSNWaiter((WaitLSNType) i);
+		}
+	}
+}
+
+/*
+ * Wait using MyLatch till the given LSN is reached, the replica gets
+ * promoted, or the postmaster dies.
+ *
+ * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was reached.
+ * Returns WAIT_LSN_RESULT_NOT_IN_RECOVERY if run not in recovery,
+ * or replica got promoted before the target LSN reached.
+ */
+WaitLSNResult
+WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
+{
+	XLogRecPtr	currentLSN;
+	TimestampTz endtime = 0;
+	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+
+	/* Shouldn't be called when shmem isn't initialized */
+	Assert(waitLSNState);
+
+	/* Should have a valid proc number */
+	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends);
+
+	if (timeout > 0)
+	{
+		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
+		wake_events |= WL_TIMEOUT;
+	}
+
+	/*
+	 * Add our process to the waiters heap.  It might happen that target LSN
+	 * gets reached before we do.  The check at the beginning of the loop
+	 * below prevents the race condition.
+	 */
+	addLSNWaiter(targetLSN, lsnType);
+
+	for (;;)
+	{
+		int			rc;
+		long		delay_ms = -1;
+
+		if (lsnType == WAIT_LSN_TYPE_REPLAY)
+			currentLSN = GetXLogReplayRecPtr(NULL);
+		else
+			currentLSN = GetFlushRecPtr(NULL);
+
+		/* Check that recovery is still in-progress */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Recovery was ended, but check if target LSN was already
+			 * reached.
+			 */
+			deleteLSNWaiter(lsnType);
+
+			if (PromoteIsTriggered() && targetLSN <= currentLSN)
+				return WAIT_LSN_RESULT_SUCCESS;
+			return WAIT_LSN_RESULT_NOT_IN_RECOVERY;
+		}
+		else
+		{
+			/* Check if the waited LSN has been reached */
+			if (targetLSN <= currentLSN)
+				break;
+		}
+
+		if (timeout > 0)
+		{
+			delay_ms = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), endtime);
+			if (delay_ms <= 0)
+				break;
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		rc = WaitLatch(MyLatch, wake_events, delay_ms,
+					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (rc & WL_POSTMASTER_DEATH)
+			ereport(FATAL,
+					errcode(ERRCODE_ADMIN_SHUTDOWN),
+					errmsg("terminating connection due to unexpected postmaster exit"),
+					errcontext("while waiting for LSN"));
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * Delete our process from the shared memory heap.  We might already be
+	 * deleted by the startup process.  The 'inHeap' flags prevents us from
+	 * the double deletion.
+	 */
+	deleteLSNWaiter(lsnType);
+
+	/*
+	 * If we didn't reach the target LSN, we must be exited by timeout.
+	 */
+	if (targetLSN > currentLSN)
+		return WAIT_LSN_RESULT_TIMEOUT;
+
+	return WAIT_LSN_RESULT_SUCCESS;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..10ffce8d174 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -24,6 +24,7 @@
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, WaitLSNShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +345,7 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+	WaitLSNShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7553f6eacef..c1ac71ff7f2 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,6 +89,8 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
@@ -355,6 +357,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+WaitLSN	"Waiting to read or update shared Wait-for-LSN state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
new file mode 100644
index 00000000000..4dc328b1b07
--- /dev/null
+++ b/src/include/access/xlogwait.h
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogwait.h
+ *	  Declarations for LSN replay waiting routines.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/xlogwait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XLOG_WAIT_H
+#define XLOG_WAIT_H
+
+#include "access/xlogdefs.h"
+#include "lib/pairingheap.h"
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/spin.h"
+#include "tcop/dest.h"
+
+/*
+ * Result statuses for WaitForLSNReplay().
+ */
+typedef enum
+{
+	WAIT_LSN_RESULT_SUCCESS,	/* Target LSN is reached */
+	WAIT_LSN_RESULT_NOT_IN_RECOVERY,	/* Recovery ended before or during our
+										 * wait */
+	WAIT_LSN_RESULT_TIMEOUT		/* Timeout occurred */
+} WaitLSNResult;
+
+/*
+ * LSN type for waiting facility.
+ */
+typedef enum WaitLSNType
+{
+	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
+	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
+	WAIT_LSN_TYPE_COUNT = 2
+} WaitLSNType;
+
+/*
+ * WaitLSNProcInfo - the shared memory structure representing information
+ * about the single process, which may wait for LSN operations.  An item of
+ * waitLSNState->procInfos array.
+ */
+typedef struct WaitLSNProcInfo
+{
+	/* LSN, which this process is waiting for */
+	XLogRecPtr	waitLSN;
+
+	/* Process to wake up once the waitLSN is reached */
+	ProcNumber	procno;
+
+	/* Heap membership flags for LSN types */
+	bool		inHeap[WAIT_LSN_TYPE_COUNT];
+
+	/* Heap nodes for LSN types */
+	pairingheap_node heapNode[WAIT_LSN_TYPE_COUNT];
+} WaitLSNProcInfo;
+
+/*
+ * WaitLSNState - the shared memory state for the LSN waiting facility.
+ */
+typedef struct WaitLSNState
+{
+	/*
+	 * The minimum LSN values some process is waiting for.  Used for the
+	 * fast-path checking if we need to wake up any waiters after replaying a
+	 * WAL record.  Could be read lock-less.  Update protected by WaitLSNLock.
+	 */
+	pg_atomic_uint64 minWaitedLSN[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * A pairing heaps of waiting processes ordered by LSN values (least LSN
+	 * is on top).  Protected by WaitLSNLock.
+	 */
+	pairingheap waitersHeap[WAIT_LSN_TYPE_COUNT];
+
+	/*
+	 * An array with per-process information, indexed by the process number.
+	 * Protected by WaitLSNLock.
+	 */
+	WaitLSNProcInfo procInfos[FLEXIBLE_ARRAY_MEMBER];
+} WaitLSNState;
+
+
+extern PGDLLIMPORT WaitLSNState *waitLSNState;
+
+extern Size WaitLSNShmemSize(void);
+extern void WaitLSNShmemInit(void);
+extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
+extern void WaitLSNCleanup(void);
+extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
+								int64 timeout);
+
+#endif							/* XLOG_WAIT_H */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 06a1ffd4b08..5b0ce383408 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -85,6 +85,7 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, WaitLSN)
 
 /*
  * There also exist several built-in LWLock tranches.  As with the predefined
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 018b5919cf6..237d33c538c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3265,6 +3265,10 @@ WaitEventIO
 WaitEventIPC
 WaitEventSet
 WaitEventTimeout
+WaitLSNType
+WaitLSNState
+WaitLSNProcInfo
+WaitLSNResult
 WaitPMResult
 WalCloseMethod
 WalCompression
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v20-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch (3.1K, ../../CAPpHfdsAsahNw7LQ1Yb6wN04RV2GGQ9XFiHnP_BDGZF=Yiickg@mail.gmail.com/3-v20-0001-Add-pairingheap_initialize-for-shared-memory-usa.patch)
  download | inline diff:
From 697d5aa28add566198bd1bccce5625bc35e1ea5a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Sun, 2 Nov 2025 11:56:53 +0800
Subject: [PATCH v20 1/3] Add pairingheap_initialize() for shared memory usage

The existing pairingheap_allocate() uses palloc(), which allocates
from process-local memory. For shared memory use cases, the pairingheap
structure must be allocated via ShmemAlloc() or embedded in a shared
memory struct. Add pairingheap_initialize() to initialize an already-
allocated pairingheap structure in-place, enabling shared memory usage.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/lib/pairingheap.c | 18 ++++++++++++++++--
 src/include/lib/pairingheap.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/lib/pairingheap.c b/src/backend/lib/pairingheap.c
index 0aef8a88f1b..fa8431f7946 100644
--- a/src/backend/lib/pairingheap.c
+++ b/src/backend/lib/pairingheap.c
@@ -44,12 +44,26 @@ pairingheap_allocate(pairingheap_comparator compare, void *arg)
 	pairingheap *heap;
 
 	heap = (pairingheap *) palloc(sizeof(pairingheap));
+	pairingheap_initialize(heap, compare, arg);
+
+	return heap;
+}
+
+/*
+ * pairingheap_initialize
+ *
+ * Same as pairingheap_allocate(), but initializes the pairing heap in-place
+ * rather than allocating a new chunk of memory.  Useful to store the pairing
+ * heap in a shared memory.
+ */
+void
+pairingheap_initialize(pairingheap *heap, pairingheap_comparator compare,
+					   void *arg)
+{
 	heap->ph_compare = compare;
 	heap->ph_arg = arg;
 
 	heap->ph_root = NULL;
-
-	return heap;
 }
 
 /*
diff --git a/src/include/lib/pairingheap.h b/src/include/lib/pairingheap.h
index 3c57d3fda1b..567586f2ecf 100644
--- a/src/include/lib/pairingheap.h
+++ b/src/include/lib/pairingheap.h
@@ -77,6 +77,9 @@ typedef struct pairingheap
 
 extern pairingheap *pairingheap_allocate(pairingheap_comparator compare,
 										 void *arg);
+extern void pairingheap_initialize(pairingheap *heap,
+								   pairingheap_comparator compare,
+								   void *arg);
 extern void pairingheap_free(pairingheap *heap);
 extern void pairingheap_add(pairingheap *heap, pairingheap_node *node);
 extern pairingheap_node *pairingheap_first(pairingheap *heap);
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v20-0003-Implement-WAIT-FOR-command.patch (44.7K, ../../CAPpHfdsAsahNw7LQ1Yb6wN04RV2GGQ9XFiHnP_BDGZF=Yiickg@mail.gmail.com/4-v20-0003-Implement-WAIT-FOR-command.patch)
  download | inline diff:
From 180a09f5d264b6b8ebd0db034ea89413751b23cd Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 3 Nov 2025 13:32:47 +0200
Subject: [PATCH v20 3/3] Implement WAIT FOR command
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

WAIT FOR is to be used on standby and specifies waiting for
the specific WAL location to be replayed.  This option is useful when
the user makes some data changes on primary and needs a guarantee to see
these changes are on standby.

WAIT FOR needs to wait without any snapshot held.  Otherwise, the snapshot
could prevent the replay of WAL records, implying a kind of self-deadlock.
This is why separate utility command seems appears to be the most robust
way to implement this functionality.  It's not possible to implement this as
a function.  Previous experience shows that stored procedures also have
limitation in this aspect.

Discussion: https://www.postgresql.org/message-id/flat/CAPpHfdsjtZLVzxjGT8rJHCYbM0D5dwkO+BBjcirozJ6nYbOW8Q@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/flat/CABPTF7UNft368x-RgOXkfj475OwEbp%2BVVO-wEXz7StgjD_%3D6sw%40mail.gmail.com
Author: Kartyshov Ivan <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Reviewed-by: Alexander Lakhin <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: jian he <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/high-availability.sgml       |  54 ++++
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/wait_for.sgml            | 234 +++++++++++++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/backend/access/transam/xact.c         |   6 +
 src/backend/access/transam/xlog.c         |   7 +
 src/backend/access/transam/xlogrecovery.c |  11 +
 src/backend/commands/Makefile             |   3 +-
 src/backend/commands/meson.build          |   1 +
 src/backend/commands/wait.c               | 212 +++++++++++++++
 src/backend/parser/gram.y                 |  33 ++-
 src/backend/storage/lmgr/proc.c           |   6 +
 src/backend/tcop/pquery.c                 |  12 +-
 src/backend/tcop/utility.c                |  22 ++
 src/include/commands/wait.h               |  22 ++
 src/include/nodes/parsenodes.h            |   8 +
 src/include/parser/kwlist.h               |   2 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/recovery/meson.build             |   3 +-
 src/test/recovery/t/049_wait_for_lsn.pl   | 302 ++++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   1 +
 21 files changed, 931 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/wait_for.sgml
 create mode 100644 src/backend/commands/wait.c
 create mode 100644 src/include/commands/wait.h
 create mode 100644 src/test/recovery/t/049_wait_for_lsn.pl

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b47d8b4106e..742deb037b7 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1376,6 +1376,60 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </sect3>
   </sect2>
 
+  <sect2 id="read-your-writes-consistency">
+   <title>Read-Your-Writes Consistency</title>
+
+   <para>
+    In asynchronous replication, there is always a short window where changes
+    on the primary may not yet be visible on the standby due to replication
+    lag. This can lead to inconsistencies when an application writes data on
+    the primary and then immediately issues a read query on the standby.
+    However, it is possible to address this without switching to synchronous
+    replication.
+   </para>
+
+   <para>
+    To address this, PostgreSQL offers a mechanism for read-your-writes
+    consistency. The key idea is to ensure that a client sees its own writes
+    by synchronizing the WAL replay on the standby with the known point of
+    change on the primary.
+   </para>
+
+   <para>
+    This is achieved by the following steps.  After performing write
+    operations, the application retrieves the current WAL location using a
+    function call like this.
+
+    <programlisting>
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+    </programlisting>
+   </para>
+
+   <para>
+    The <acronym>LSN</acronym> obtained from the primary is then communicated
+    to the standby server. This can be managed at the application level or
+    via the connection pooler.  On the standby, the application issues the
+    <xref linkend="sql-wait-for"/> command to block further processing until
+    the standby's WAL replay process reaches (or exceeds) the specified
+    <acronym>LSN</acronym>.
+
+    <programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+    </programlisting>
+    Once the command returns a status of success, it guarantees that all
+    changes up to the provided <acronym>LSN</acronym> have been applied,
+    ensuring that subsequent read queries will reflect the latest updates.
+   </para>
+  </sect2>
+
   <sect2 id="continuous-archiving-in-standby">
    <title>Continuous Archiving in Standby</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..e167406c744 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -188,6 +188,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY update             SYSTEM "update.sgml">
 <!ENTITY vacuum             SYSTEM "vacuum.sgml">
 <!ENTITY values             SYSTEM "values.sgml">
+<!ENTITY waitFor            SYSTEM "wait_for.sgml">
 
 <!-- applications and utilities -->
 <!ENTITY clusterdb          SYSTEM "clusterdb.sgml">
diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
new file mode 100644
index 00000000000..3b8e842d1de
--- /dev/null
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -0,0 +1,234 @@
+<!--
+doc/src/sgml/ref/wait_for.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-wait-for">
+ <indexterm zone="sql-wait-for">
+  <primary>WAIT FOR</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>WAIT FOR</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>WAIT FOR</refname>
+  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+    TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
+    NO_THROW
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+    Waits until recovery replays <parameter>lsn</parameter>.
+    If no <parameter>timeout</parameter> is specified or it is set to
+    zero, this command waits indefinitely for the
+    <parameter>lsn</parameter>.
+    On timeout, or if the server is promoted before
+    <parameter>lsn</parameter> is reached, an error is emitted,
+    unless <literal>NO_THROW</literal> is specified in the WITH clause.
+    If <parameter>NO_THROW</parameter> is specified, then the command
+    doesn't throw errors.
+  </para>
+
+  <para>
+    The possible return values are <literal>success</literal>,
+    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">lsn</replaceable></term>
+    <listitem>
+     <para>
+      Specifies the target <acronym>LSN</acronym> to wait for.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies optional parameters for the wait operation.
+      The following parameters are supported:
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
+        <listitem>
+         <para>
+          When specified and <parameter>timeout</parameter> is greater than zero,
+          the command waits until <parameter>lsn</parameter> is reached or
+          the specified <parameter>timeout</parameter> has elapsed.
+         </para>
+         <para>
+          The <parameter>timeout</parameter> might be given as integer number of
+          milliseconds.  Also it might be given as string literal with
+          integer number of milliseconds or a number with unit
+          (see <xref linkend="config-setting-names-values"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
+        <term><literal>NO_THROW</literal></term>
+        <listitem>
+         <para>
+          Specify to not throw an error in the case of timeout or
+          running on the primary.  In this case the result status can be get from
+          the return value.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Outputs</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>success</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that we have successfully reached
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>timeout</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the timeout happened before reaching
+      the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>not in recovery</literal></term>
+    <listitem>
+     <para>
+      This return value denotes that the database server is not in a recovery
+      state.  This might mean either the database server was not in recovery
+      at the moment of receiving the command, or it was promoted before
+      reaching the target <parameter>lsn</parameter>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+    <command>WAIT FOR</command> command waits till
+    <parameter>lsn</parameter> to be replayed on standby.
+    That is, after this command execution, the value returned by
+    <function>pg_last_wal_replay_lsn</function> should be greater or equal
+    to the <parameter>lsn</parameter> value.  This is useful to achieve
+    read-your-writes-consistency, while using async replica for reads and
+    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
+    modification should be stored on the client application side or the
+    connection pooler side.
+  </para>
+
+  <para>
+    <command>WAIT FOR</command> command should be called on standby.
+    If a user runs <command>WAIT FOR</command> on primary, it
+    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
+    However, if <command>WAIT FOR</command> is
+    called on primary promoted from standby and <literal>lsn</literal>
+    was already replayed, then the <command>WAIT FOR</command> command just
+    exits immediately.
+  </para>
+
+</refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+    You can use <command>WAIT FOR</command> command to wait for
+    the <type>pg_lsn</type> value.  For example, an application could update
+    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+    on primary server to get the <acronym>lsn</acronym> given that
+    <varname>synchronous_commit</varname> could be set to
+    <literal>off</literal>.
+
+   <programlisting>
+postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
+UPDATE 100
+postgres=# SELECT pg_current_wal_insert_lsn();
+pg_current_wal_insert_lsn
+--------------------
+0/306EE20
+(1 row)
+</programlisting>
+
+   Then an application could run <command>WAIT FOR</command>
+   with the <parameter>lsn</parameter> obtained from primary.  After that the
+   changes made on primary should be guaranteed to be visible on replica.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20';
+ status
+--------
+ success
+(1 row)
+postgres=# SELECT * FROM movie WHERE genre = 'Drama';
+ genre
+-------
+(0 rows)
+</programlisting>
+  </para>
+
+  <para>
+    If the target LSN is not reached before the timeout, the error is thrown.
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
+</programlisting>
+  </para>
+
+  <para>
+   The same example uses <command>WAIT FOR</command> with
+   <parameter>NO_THROW</parameter> option.
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+ status
+--------
+ timeout
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2cf02c37b17 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -216,6 +216,7 @@
    &update;
    &vacuum;
    &values;
+   &waitFor;
 
  </reference>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2cf3d4e92b7..092e197eba3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -31,6 +31,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -2843,6 +2844,11 @@ AbortTransaction(void)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Clear wait information and command progress indicator */
 	pgstat_report_wait_end();
 	pgstat_progress_end_command();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fd91bcd68ec..45a16bd1ec2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -62,6 +62,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
@@ -6227,6 +6228,12 @@ StartupXLOG(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	/*
+	 * Wake up all waiters for replay LSN.  They need to report an error that
+	 * recovery was ended before reaching the target LSN.
+	 */
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+
 	/*
 	 * Shutdown the recovery environment.  This must occur after
 	 * RecoverPreparedTransactions() (see notes in lock_twophase_recover())
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..0e51148110f 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -40,6 +40,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "backup/basebackup.h"
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
@@ -1838,6 +1839,16 @@ PerformWalRecovery(void)
 				break;
 			}
 
+			/*
+			 * If we replayed an LSN that someone was waiting for then walk
+			 * over the shared memory array and set latches to notify the
+			 * waiters.
+			 */
+			if (waitLSNState &&
+				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..f99acfd2b4b 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -64,6 +64,7 @@ OBJS = \
 	vacuum.o \
 	vacuumparallel.o \
 	variable.o \
-	view.o
+	view.o \
+	wait.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..9f640ad4810 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -53,4 +53,5 @@ backend_sources += files(
   'vacuumparallel.c',
   'variable.c',
   'view.c',
+  'wait.c',
 )
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
new file mode 100644
index 00000000000..67068a92dbf
--- /dev/null
+++ b/src/backend/commands/wait.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.c
+ *	  Implements WAIT FOR, which allows waiting for events such as
+ *	  time passing or LSN having been replayed on replica.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/wait.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
+#include "commands/defrem.h"
+#include "commands/wait.h"
+#include "executor/executor.h"
+#include "parser/parse_node.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/pg_lsn.h"
+#include "utils/snapmgr.h"
+
+
+void
+ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
+{
+	XLogRecPtr	lsn;
+	int64		timeout = 0;
+	WaitLSNResult waitLSNResult;
+	bool		throw = true;
+	TupleDesc	tupdesc;
+	TupOutputState *tstate;
+	const char *result = "<unset>";
+	bool		timeout_specified = false;
+	bool		no_throw_specified = false;
+
+	/* Parse and validate the mandatory LSN */
+	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
+										  CStringGetDatum(stmt->lsn_literal)));
+
+	foreach_node(DefElem, defel, stmt->options)
+	{
+		if (strcmp(defel->defname, "timeout") == 0)
+		{
+			char	   *timeout_str;
+			const char *hintmsg;
+			double		result;
+
+			if (timeout_specified)
+				errorConflictingDefElem(defel, pstate);
+			timeout_specified = true;
+
+			timeout_str = defGetString(defel);
+
+			if (!parse_real(timeout_str, &result, GUC_UNIT_MS, &hintmsg))
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid timeout value: \"%s\"", timeout_str),
+						hintmsg ? errhint("%s", _(hintmsg)) : 0);
+			}
+
+			/*
+			 * Get rid of any fractional part in the input. This is so we
+			 * don't fail on just-out-of-range values that would round into
+			 * range.
+			 */
+			result = rint(result);
+
+			/* Range check */
+			if (unlikely(isnan(result) || !FLOAT8_FITS_IN_INT64(result)))
+				ereport(ERROR,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("timeout value is out of range"));
+
+			if (result < 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("timeout cannot be negative"));
+
+			timeout = (int64) result;
+		}
+		else if (strcmp(defel->defname, "no_throw") == 0)
+		{
+			if (no_throw_specified)
+				errorConflictingDefElem(defel, pstate);
+
+			no_throw_specified = true;
+
+			throw = !defGetBoolean(defel);
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("option \"%s\" not recognized",
+						   defel->defname),
+					parser_errposition(pstate, defel->location));
+		}
+	}
+
+	/*
+	 * We are going to wait for the LSN replay.  We should first care that we
+	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
+	 * command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be only called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+
+	/*
+	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * needed.
+	 */
+	switch (waitLSNResult)
+	{
+		case WAIT_LSN_RESULT_SUCCESS:
+			/* Nothing to do on success */
+			result = "success";
+			break;
+
+		case WAIT_LSN_RESULT_TIMEOUT:
+			if (throw)
+				ereport(ERROR,
+						errcode(ERRCODE_QUERY_CANCELED),
+						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+							   LSN_FORMAT_ARGS(lsn),
+							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			else
+				result = "timeout";
+			break;
+
+		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
+			if (throw)
+			{
+				if (PromoteIsTriggered())
+				{
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+									  LSN_FORMAT_ARGS(lsn),
+									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+				}
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("recovery is not in progress"),
+							errhint("Waiting for the replay LSN can only be executed during recovery."));
+			}
+			else
+				result = "not in recovery";
+			break;
+	}
+
+	/* need a tuple descriptor representing a single TEXT column */
+	tupdesc = WaitStmtResultDesc(stmt);
+
+	/* prepare for projection of tuples */
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
+
+	/* Send it */
+	do_text_output_oneline(tstate, result);
+
+	end_tup_output(tstate);
+}
+
+TupleDesc
+WaitStmtResultDesc(WaitStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	/* Need a tuple descriptor representing a single TEXT  column */
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
+					   TEXTOID, -1, 0);
+	return tupdesc;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4b29c822e8..a4e6f80504b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -308,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
-		ViewStmt CheckPointStmt CreateConversionStmt
+		ViewStmt WaitStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
@@ -325,6 +325,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
+%type <list>		opt_wait_with_clause
 %type <list>		utility_option_list
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
@@ -678,7 +679,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
+	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -792,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -1120,6 +1120,7 @@ stmt:
 			| VariableSetStmt
 			| VariableShowStmt
 			| ViewStmt
+			| WaitStmt
 			| /*EMPTY*/
 				{ $$ = NULL; }
 		;
@@ -16462,6 +16463,26 @@ xml_passing_mech:
 			| BY VALUE_P
 		;
 
+/*****************************************************************************
+ *
+ * WAIT FOR LSN
+ *
+ *****************************************************************************/
+
+WaitStmt:
+			WAIT FOR LSN_P Sconst opt_wait_with_clause
+				{
+					WaitStmt *n = makeNode(WaitStmt);
+					n->lsn_literal = $4;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+			;
+
+opt_wait_with_clause:
+			WITH '(' utility_option_list ')'		{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
 
 /*
  * Aggregate decoration clauses
@@ -17949,6 +17970,7 @@ unreserved_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18119,6 +18141,7 @@ unreserved_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHITESPACE_P
 			| WITHIN
 			| WITHOUT
@@ -18565,6 +18588,7 @@ bare_label_keyword:
 			| LOCK_P
 			| LOCKED
 			| LOGGED
+			| LSN_P
 			| MAPPING
 			| MATCH
 			| MATCHED
@@ -18776,6 +18800,7 @@ bare_label_keyword:
 			| VIEWS
 			| VIRTUAL
 			| VOLATILE
+			| WAIT
 			| WHEN
 			| WHITESPACE_P
 			| WORK
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 96f29aafc39..26b201eadb8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -36,6 +36,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -947,6 +948,11 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
+	/*
+	 * Cleanup waiting for LSN if any.
+	 */
+	WaitLSNCleanup();
+
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 74179139fa9..fde78c55160 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1158,10 +1158,11 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 	MemoryContextSwitchTo(portal->portalContext);
 
 	/*
-	 * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
-	 * under us, so don't complain if it's now empty.  Otherwise, our snapshot
-	 * should be the top one; pop it.  Note that this could be a different
-	 * snapshot from the one we made above; see EnsurePortalSnapshotExists.
+	 * Some utility commands (e.g., VACUUM, WAIT FOR) pop the ActiveSnapshot
+	 * stack from under us, so don't complain if it's now empty.  Otherwise,
+	 * our snapshot should be the top one; pop it.  Note that this could be a
+	 * different snapshot from the one we made above; see
+	 * EnsurePortalSnapshotExists.
 	 */
 	if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
 	{
@@ -1738,7 +1739,8 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
 		IsA(utilityStmt, ListenStmt) ||
 		IsA(utilityStmt, NotifyStmt) ||
 		IsA(utilityStmt, UnlistenStmt) ||
-		IsA(utilityStmt, CheckPointStmt))
+		IsA(utilityStmt, CheckPointStmt) ||
+		IsA(utilityStmt, WaitStmt))
 		return false;
 
 	return true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..082967c0a86 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -56,6 +56,7 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/view.h"
+#include "commands/wait.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
 #include "postmaster/bgwriter.h"
@@ -266,6 +267,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_PrepareStmt:
 		case T_UnlistenStmt:
 		case T_VariableSetStmt:
+		case T_WaitStmt:
 			{
 				/*
 				 * These modify only backend-local state, so they're OK to run
@@ -1055,6 +1057,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_WaitStmt:
+			{
+				ExecWaitStmt(pstate, (WaitStmt *) parsetree, dest);
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2059,6 +2067,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_VariableShowStmt:
 			return true;
 
+		case T_WaitStmt:
+			return true;
+
 		default:
 			return false;
 	}
@@ -2114,6 +2125,9 @@ UtilityTupleDescriptor(Node *parsetree)
 				return GetPGVariableResultDesc(n->name);
 			}
 
+		case T_WaitStmt:
+			return WaitStmtResultDesc((WaitStmt *) parsetree);
+
 		default:
 			return NULL;
 	}
@@ -3091,6 +3105,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_WaitStmt:
+			tag = CMDTAG_WAIT;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
@@ -3689,6 +3707,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_WaitStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/include/commands/wait.h b/src/include/commands/wait.h
new file mode 100644
index 00000000000..ce332134fb3
--- /dev/null
+++ b/src/include/commands/wait.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait.h
+ *	  prototypes for commands/wait.c
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * src/include/commands/wait.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_H
+#define WAIT_H
+
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/dest.h"
+
+extern void ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest);
+extern TupleDesc WaitStmtResultDesc(WaitStmt *stmt);
+
+#endif							/* WAIT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecbddd12e1b..d14294a4ece 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,4 +4385,12 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct WaitStmt
+{
+	NodeTag		type;
+	char	   *lsn_literal;	/* LSN string from grammar */
+	List	   *options;		/* List of DefElem nodes */
+} WaitStmt;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..5d4fe27ef96 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -270,6 +270,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("lsn", LSN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -496,6 +497,7 @@ PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("wait", WAIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..c4606d65043 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -217,3 +217,4 @@ PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
 PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
 PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+PG_CMDTAG(CMDTAG_WAIT, "WAIT", false, false, false)
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 52993c32dbb..523a5cd5b52 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -56,7 +56,8 @@ tests += {
       't/045_archive_restartpoint.pl',
       't/046_checkpoint_logical_slot.pl',
       't/047_checkpoint_physical_slot.pl',
-      't/048_vacuum_horizon_floor.pl'
+      't/048_vacuum_horizon_floor.pl',
+      't/049_wait_for_lsn.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
new file mode 100644
index 00000000000..e0ddb06a2f0
--- /dev/null
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -0,0 +1,302 @@
+# Checks waiting for the LSN replay on standby using
+# the WAIT FOR command.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content and take a backup
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a");
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a streaming standby with a 1 second delay from the backup
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $delay = 1;
+$node_standby->init_from_backup($node_primary, $backup_name,
+	has_streaming => 1);
+$node_standby->append_conf(
+	'postgresql.conf', qq[
+	recovery_min_apply_delay = '${delay}s'
+]);
+$node_standby->start;
+
+# 1. Make sure that WAIT FOR works: add new content to
+# primary and memorize primary's insert LSN, then wait for that LSN to be
+# replayed on standby.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(11, 20))");
+my $lsn1 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn1}' WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${lsn1}'::pg_lsn);
+]);
+
+# Make sure the current LSN on standby is at least as big as the LSN we
+# observed on primary's before.
+ok((split("\n", $output))[-1] >= 0,
+	"standby reached the same LSN as primary after WAIT FOR");
+
+# 2. Check that new data is visible after calling WAIT FOR
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(21, 30))");
+my $lsn2 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}';
+	SELECT count(*) FROM wait_test;
+]);
+
+# Make sure the count(*) on standby reflects the recent changes on primary
+ok((split("\n", $output))[-1] eq 30,
+	"standby reached the same LSN as primary");
+
+# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# unreachable LSN must be well in advance.  So WAL records issued by
+# the concurrent autovacuum could not affect that.
+my $lsn3 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $stderr;
+$node_standby->safe_psql('postgres',
+	"WAIT FOR LSN '${lsn2}' WITH (timeout '10ms');");
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (timeout '1000ms');",
+	stderr => \$stderr);
+ok( $stderr =~ /timed out while waiting for target LSN/,
+	"get timeout on waiting for unreachable LSN");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success",
+	"WAIT FOR returns correct status after successful waiting");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
+
+# 4. Check that WAIT FOR triggers an error if called on primary,
+# within another function, or inside a transaction with an isolation level
+# higher than READ COMMITTED.
+
+$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~ /recovery is not in progress/,
+	"get an error when running on the primary");
+
+$node_standby->psql(
+	'postgres',
+	"BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running in a transaction with an isolation level higher than REPEATABLE READ"
+);
+
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION pg_wal_replay_wait_wrap(target_lsn pg_lsn) RETURNS void AS \$\$
+  BEGIN
+    EXECUTE format('WAIT FOR LSN %L;', target_lsn);
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
+$node_primary->wait_for_catchup($node_standby);
+$node_standby->psql(
+	'postgres',
+	"SELECT pg_wal_replay_wait_wrap('${lsn3}');",
+	stderr => \$stderr);
+ok( $stderr =~
+	  /WAIT FOR must be only called without an active or registered snapshot/,
+	"get an error when running within another function");
+
+# 5. Check parameter validation error cases on standby before promotion
+my $test_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Test negative timeout
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '-1000ms');",
+	stderr => \$stderr);
+ok($stderr =~ /timeout cannot be negative/, "get error for negative timeout");
+
+# Test unknown parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (unknown_param 'value');",
+	stderr => \$stderr);
+ok($stderr =~ /option "unknown_param" not recognized/,
+	"get error for unknown parameter");
+
+# Test duplicate TIMEOUT parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout '1000', timeout '2000');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate TIMEOUT parameter");
+
+# Test duplicate NO_THROW parameter with WITH clause
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (no_throw, no_throw);",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate NO_THROW parameter");
+
+# Test syntax error - options without WITH keyword
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' (timeout '100ms');",
+	stderr => \$stderr);
+ok($stderr =~ /syntax error/,
+	"get syntax error when options specified without WITH keyword");
+
+# Test syntax error - missing LSN
+$node_standby->psql('postgres', "WAIT FOR TIMEOUT 1000;", stderr => \$stderr);
+ok($stderr =~ /syntax error/, "get syntax error for missing LSN");
+
+# Test invalid LSN format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN 'invalid_lsn';",
+	stderr => \$stderr);
+ok($stderr =~ /invalid input syntax for type pg_lsn/,
+	"get error for invalid LSN format");
+
+# Test invalid timeout format
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (timeout 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /invalid timeout value/,
+	"get error for invalid timeout format");
+
+# Test new WITH clause syntax
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn2}' WITH (timeout '0.1s', no_throw);]);
+ok($output eq "success", "WAIT FOR WITH clause syntax works correctly");
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn3}' WITH (timeout 100, no_throw);]);
+ok($output eq "timeout",
+	"WAIT FOR WITH clause returns correct timeout status");
+
+# Test WITH clause error case - invalid option
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (invalid_option 'value');",
+	stderr => \$stderr);
+ok( $stderr =~ /option "invalid_option" not recognized/,
+	"get error for invalid WITH clause option");
+
+# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# psql sessions each waiting for a corresponding insertion.  When waiting is
+# finished, stored procedures logs if there are visible as many rows as
+# should be.
+$node_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
+  DECLARE
+    count int;
+  BEGIN
+    SELECT count(*) FROM wait_test INTO count;
+    IF count >= 31 + i THEN
+      RAISE LOG 'count %', i;
+    END IF;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+my @psql_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (${i});");
+	my $lsn =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$psql_sessions[$i] = $node_standby->background_psql('postgres');
+	$psql_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn}';
+		SELECT log_count(${i});
+	]);
+}
+my $log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("count ${i}", $log_offset);
+	$psql_sessions[$i]->quit;
+}
+
+ok(1, 'multiple LSN waiters reported consistent data');
+
+# 7. Check that the standby promotion terminates the wait on LSN.  Start
+# waiting for an unreachable LSN then promote.  Check the log for the relevant
+# error message.  Also, check that waiting for already replayed LSN doesn't
+# cause an error even after promotion.
+my $lsn4 =
+  $node_primary->safe_psql('postgres',
+	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+my $lsn5 =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+my $psql_session = $node_standby->background_psql('postgres');
+$psql_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${lsn4}';
+]);
+
+# Make sure standby will be promoted at least at the primary insert LSN we
+# have just observed.  Use pg_switch_wal() to force the insert LSN to be
+# written then wait for standby to catchup.
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal();');
+$node_primary->wait_for_catchup($node_standby);
+
+$log_offset = -s $node_standby->logfile;
+$node_standby->promote;
+$node_standby->wait_for_log('recovery is not in progress', $log_offset);
+
+ok(1, 'got error after standby promote');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+
+ok(1, 'wait for already replayed LSN exits immediately even after promotion');
+
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+ok($output eq "not in recovery",
+	"WAIT FOR returns correct status after standby promotion");
+
+
+$node_standby->stop;
+$node_primary->stop;
+
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
+$psql_session->{run}->finish;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 237d33c538c..e34dcf97df8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3270,6 +3270,7 @@ WaitLSNState
 WaitLSNProcInfo
 WaitLSNResult
 WaitPMResult
+WaitStmt
 WalCloseMethod
 WalCompression
 WalInsertClass
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-03 15:06                                                           ` Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-03 15:06 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On 2025-Nov-03, Alexander Korotkov wrote:

> I'd like to give this subject another chance for pg19.  I'm going to
> push this if no objections.

Sure.  I don't understand why patches 0002 and 0003 are separate though.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2025-11-03 15:13                                                             ` Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Andres Freund @ 2025-11-03 15:13 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Xuneng Zhou <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> On 2025-Nov-03, Alexander Korotkov wrote:
> 
> > I'd like to give this subject another chance for pg19.  I'm going to
> > push this if no objections.
> 
> Sure.  I don't understand why patches 0002 and 0003 are separate though.

FWIW, I appreciate such splits. Even if the functionality isn't usable
independently, it's still different type of code that's affected. And the
patches are each big enough to make that worthwhile for easier review.

One thing that'd be nice to do once we have WAIT FOR is to make the common
case of wait_for_catchup() use this facility, instead of polling...

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2025-11-05 09:51                                                               ` Alexander Korotkov <[email protected]>
  2025-11-05 14:03                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-13 20:32                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  0 siblings, 3 replies; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-05 09:51 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Xuneng Zhou <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi!

On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > On 2025-Nov-03, Alexander Korotkov wrote:
> >
> > > I'd like to give this subject another chance for pg19.  I'm going to
> > > push this if no objections.
> >
> > Sure.  I don't understand why patches 0002 and 0003 are separate though.
>
> FWIW, I appreciate such splits. Even if the functionality isn't usable
> independently, it's still different type of code that's affected. And the
> patches are each big enough to make that worthwhile for easier review.

Thank you for the feedback, pushed.

> One thing that'd be nice to do once we have WAIT FOR is to make the common
> case of wait_for_catchup() use this facility, instead of polling...

The draft patch for that is attached.  WAIT FOR doesn't handle all the
possible use cases of wait_for_catchup(), but I've added usage when
it's appropriate.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v1-0001-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (2.2K, ../../CAPpHfdtK8NRxo1-u6kh1DjRGpcngBnHr5bahiY_5utWAbOjBDQ@mail.gmail.com/2-v1-0001-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From bb12721dc3efbd213416adb8c3563bb0c11c023b Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Wed, 5 Nov 2025 11:10:04 +0200
Subject: [PATCH v1] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 27 ++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 35413f14019..85b5d9863cd 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3328,6 +3328,8 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3340,8 +3342,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
 		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
@@ -3360,6 +3360,29 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when appropriate
+	if (($mode eq 'replay') && ($isrecovery eq 't'))
+	{
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' WITH (timeout '${PostgreSQL::Test::Utils::timeout_default}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+	${output});
+			diag qq(Last pg_stat_replication contents:
+	${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-05 14:03                                                                 ` Xuneng Zhou <[email protected]>
  2025-11-07 22:02                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-05 14:03 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
>
> Hi!
>
> On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > On 2025-Nov-03, Alexander Korotkov wrote:
> > >
> > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > push this if no objections.
> > >
> > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> >
> > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > independently, it's still different type of code that's affected. And the
> > patches are each big enough to make that worthwhile for easier review.
>
> Thank you for the feedback, pushed.

Thanks for pushing them!

> > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > case of wait_for_catchup() use this facility, instead of polling...
>
> The draft patch for that is attached.  WAIT FOR doesn't handle all the
> possible use cases of wait_for_catchup(), but I've added usage when
> it's appropriate.

Interesting, could this approach be extended to the flush and other
modes as well? I might need to spend some time to understand it before
I can provide a meaningful review.

Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-05 14:03                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-07 22:02                                                                   ` Alexander Korotkov <[email protected]>
  2025-11-16 12:08                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-07 22:02 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Nov 5, 2025 at 4:03 PM Xuneng Zhou <[email protected]> wrote:
>
> On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
> > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > > On 2025-Nov-03, Alexander Korotkov wrote:
> > > >
> > > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > > push this if no objections.
> > > >
> > > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > >
> > > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > independently, it's still different type of code that's affected. And the
> > > patches are each big enough to make that worthwhile for easier review.
> >
> > Thank you for the feedback, pushed.
>
> Thanks for pushing them!
>
> > > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > > case of wait_for_catchup() use this facility, instead of polling...
> >
> > The draft patch for that is attached.  WAIT FOR doesn't handle all the
> > possible use cases of wait_for_catchup(), but I've added usage when
> > it's appropriate.
>
> Interesting, could this approach be extended to the flush and other
> modes as well? I might need to spend some time to understand it before
> I can provide a meaningful review.

I think we might end up extending WaitLSNType enum.  However, I hate
inHeap and heapNode arrays growing in WaitLSNProcInfo as they are
allocated per process.  I found that we could optimize WaitLSNProcInfo
struct turning them into simple variables because a single process can
wait only for a single LSN at a time.  Please, check the attached
patch.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v1-0001-Optimize-shared-memory-usage-for-WaitLSNProcInfo.patch (5.3K, ../../CAPpHfdsBR-7sDtXFJ1qpJtKiohfGoj=vqzKVjWxtWsWidx7G_A@mail.gmail.com/2-v1-0001-Optimize-shared-memory-usage-for-WaitLSNProcInfo.patch)
  download | inline diff:
From 89fde94dd74810d2bf349af33b7ca9585080c0f6 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Fri, 7 Nov 2025 23:49:47 +0200
Subject: [PATCH v1] Optimize shared memory usage for WaitLSNProcInfo

We need separate pairing heaps for different WaitLSNType's, because there
might be waiters for different LSN's at the same time.  However, one process
can wait only for one type of LSN at a time.  So, not need for inHeap
and heapNode fields to be arrays.
---
 src/backend/access/transam/xlogwait.c | 40 ++++++++++++---------------
 src/include/access/xlogwait.h         |  7 +++--
 2 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 34fa41ed9b2..e1eb21be125 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -90,7 +90,7 @@ WaitLSNShmemInit(void)
 		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
 		{
 			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
-			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, (void *) (uintptr_t) i);
+			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, NULL);
 		}
 
 		/* Initialize process info array */
@@ -106,9 +106,8 @@ WaitLSNShmemInit(void)
 static int
 waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
 {
-	int			i = (uintptr_t) arg;
-	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], a);
-	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], b);
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode, b);
 
 	if (aproc->waitLSN < bproc->waitLSN)
 		return 1;
@@ -132,7 +131,7 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 	if (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
 	{
 		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
-		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode, node);
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
@@ -154,10 +153,11 @@ addLSNWaiter(XLogRecPtr lsn, WaitLSNType lsnType)
 
 	procInfo->procno = MyProcNumber;
 	procInfo->waitLSN = lsn;
+	procInfo->lsnType = lsnType;
 
-	Assert(!procInfo->inHeap[i]);
-	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
-	procInfo->inHeap[i] = true;
+	Assert(!procInfo->inHeap);
+	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode);
+	procInfo->inHeap = true;
 	updateMinWaitedLSN(lsnType);
 
 	LWLockRelease(WaitLSNLock);
@@ -176,10 +176,10 @@ deleteLSNWaiter(WaitLSNType lsnType)
 
 	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
 
-	if (procInfo->inHeap[i])
+	if (procInfo->inHeap)
 	{
-		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
-		procInfo->inHeap[i] = false;
+		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode);
+		procInfo->inHeap = false;
 		updateMinWaitedLSN(lsnType);
 	}
 
@@ -228,7 +228,7 @@ wakeupWaiters(WaitLSNType lsnType, XLogRecPtr currentLSN)
 			WaitLSNProcInfo *procInfo;
 
 			/* Get procInfo using appropriate heap node */
-			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode, node);
 
 			if (XLogRecPtrIsValid(currentLSN) && procInfo->waitLSN > currentLSN)
 				break;
@@ -238,7 +238,7 @@ wakeupWaiters(WaitLSNType lsnType, XLogRecPtr currentLSN)
 			(void) pairingheap_remove_first(&waitLSNState->waitersHeap[i]);
 
 			/* Update appropriate flag */
-			procInfo->inHeap[i] = false;
+			procInfo->inHeap = false;
 
 			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
 				break;
@@ -285,20 +285,14 @@ WaitLSNCleanup(void)
 {
 	if (waitLSNState)
 	{
-		int			i;
-
 		/*
-		 * We do a fast-path check of the heap flags without the lock.  These
-		 * flags are set to true only by the process itself.  So, it's only
+		 * We do a fast-path check of the inHeap flag without the lock.  This
+		 * flag is set to true only by the process itself.  So, it's only
 		 * possible to get a false positive.  But that will be eliminated by a
 		 * recheck inside deleteLSNWaiter().
 		 */
-
-		for (i = 0; i < (int) WAIT_LSN_TYPE_COUNT; i++)
-		{
-			if (waitLSNState->procInfos[MyProcNumber].inHeap[i])
-				deleteLSNWaiter((WaitLSNType) i);
-		}
+		if (waitLSNState->procInfos[MyProcNumber].inHeap)
+			deleteLSNWaiter(waitLSNState->procInfos[MyProcNumber].lsnType);
 	}
 }
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 4dc328b1b07..46bac74988b 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -50,14 +50,17 @@ typedef struct WaitLSNProcInfo
 	/* LSN, which this process is waiting for */
 	XLogRecPtr	waitLSN;
 
+	/* The type of LSN to wait */
+	WaitLSNType lsnType;
+
 	/* Process to wake up once the waitLSN is reached */
 	ProcNumber	procno;
 
 	/* Heap membership flags for LSN types */
-	bool		inHeap[WAIT_LSN_TYPE_COUNT];
+	bool		inHeap;
 
 	/* Heap nodes for LSN types */
-	pairingheap_node heapNode[WAIT_LSN_TYPE_COUNT];
+	pairingheap_node heapNode;
 } WaitLSNProcInfo;
 
 /*
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-05 14:03                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-07 22:02                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-16 12:08                                                                     ` Alexander Korotkov <[email protected]>
  2025-11-16 13:30                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-16 12:08 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Sat, Nov 8, 2025 at 12:02 AM Alexander Korotkov <[email protected]> wrote:
> On Wed, Nov 5, 2025 at 4:03 PM Xuneng Zhou <[email protected]> wrote:
> > On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
> > > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > > > On 2025-Nov-03, Alexander Korotkov wrote:
> > > > >
> > > > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > > > push this if no objections.
> > > > >
> > > > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > > >
> > > > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > > independently, it's still different type of code that's affected. And the
> > > > patches are each big enough to make that worthwhile for easier review.
> > >
> > > Thank you for the feedback, pushed.
> >
> > Thanks for pushing them!
> >
> > > > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > > > case of wait_for_catchup() use this facility, instead of polling...
> > >
> > > The draft patch for that is attached.  WAIT FOR doesn't handle all the
> > > possible use cases of wait_for_catchup(), but I've added usage when
> > > it's appropriate.
> >
> > Interesting, could this approach be extended to the flush and other
> > modes as well? I might need to spend some time to understand it before
> > I can provide a meaningful review.
>
> I think we might end up extending WaitLSNType enum.  However, I hate
> inHeap and heapNode arrays growing in WaitLSNProcInfo as they are
> allocated per process.  I found that we could optimize WaitLSNProcInfo
> struct turning them into simple variables because a single process can
> wait only for a single LSN at a time.  Please, check the attached
> patch.

Here is the updated patch integrating minor corrections provided by
Xuneng Zhou off-list.  I'm going to push this if no objections.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v3-0001-Optimize-shared-memory-usage-for-WaitLSNProcInfo.patch (5.7K, ../../CAPpHfdtb7iLMWBGieii5a_xbG07WocB4dbwfsHhnE7tOiGPVyQ@mail.gmail.com/2-v3-0001-Optimize-shared-memory-usage-for-WaitLSNProcInfo.patch)
  download | inline diff:
From 09c5f97fac0b14d82d2108d4b31777c7a639608e Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sun, 16 Nov 2025 14:06:50 +0200
Subject: [PATCH v3] Optimize shared memory usage for WaitLSNProcInfo

We need separate pairing heaps for different WaitLSNType's, because there
might be waiters for different LSN's at the same time.  However, one process
can wait only for one type of LSN at a time.  So, no need for inHeap
and heapNode fields to be arrays.

Discussion: https://postgr.es/m/CAPpHfdsBR-7sDtXFJ1qpJtKiohfGoj%3DvqzKVjWxtWsWidx7G_A%40mail.gmail.com
Author: Alexander Korotkov <[email protected]>
Reviewed-by: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 42 ++++++++++++---------------
 src/include/access/xlogwait.h         | 14 ++++++---
 2 files changed, 29 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 78de93db47f..98aa5f1e4a2 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -90,7 +90,7 @@ WaitLSNShmemInit(void)
 		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
 		{
 			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
-			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, (void *) (uintptr_t) i);
+			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, NULL);
 		}
 
 		/* Initialize process info array */
@@ -106,9 +106,8 @@ WaitLSNShmemInit(void)
 static int
 waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
 {
-	int			i = (uintptr_t) arg;
-	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], a);
-	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode[i], b);
+	const WaitLSNProcInfo *aproc = pairingheap_const_container(WaitLSNProcInfo, heapNode, a);
+	const WaitLSNProcInfo *bproc = pairingheap_const_container(WaitLSNProcInfo, heapNode, b);
 
 	if (aproc->waitLSN < bproc->waitLSN)
 		return 1;
@@ -132,7 +131,7 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 	if (!pairingheap_is_empty(&waitLSNState->waitersHeap[i]))
 	{
 		pairingheap_node *node = pairingheap_first(&waitLSNState->waitersHeap[i]);
-		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+		WaitLSNProcInfo *procInfo = pairingheap_container(WaitLSNProcInfo, heapNode, node);
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
@@ -154,10 +153,11 @@ addLSNWaiter(XLogRecPtr lsn, WaitLSNType lsnType)
 
 	procInfo->procno = MyProcNumber;
 	procInfo->waitLSN = lsn;
+	procInfo->lsnType = lsnType;
 
-	Assert(!procInfo->inHeap[i]);
-	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
-	procInfo->inHeap[i] = true;
+	Assert(!procInfo->inHeap);
+	pairingheap_add(&waitLSNState->waitersHeap[i], &procInfo->heapNode);
+	procInfo->inHeap = true;
 	updateMinWaitedLSN(lsnType);
 
 	LWLockRelease(WaitLSNLock);
@@ -176,10 +176,12 @@ deleteLSNWaiter(WaitLSNType lsnType)
 
 	LWLockAcquire(WaitLSNLock, LW_EXCLUSIVE);
 
-	if (procInfo->inHeap[i])
+	Assert(procInfo->lsnType == lsnType);
+
+	if (procInfo->inHeap)
 	{
-		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode[i]);
-		procInfo->inHeap[i] = false;
+		pairingheap_remove(&waitLSNState->waitersHeap[i], &procInfo->heapNode);
+		procInfo->inHeap = false;
 		updateMinWaitedLSN(lsnType);
 	}
 
@@ -228,7 +230,7 @@ wakeupWaiters(WaitLSNType lsnType, XLogRecPtr currentLSN)
 			WaitLSNProcInfo *procInfo;
 
 			/* Get procInfo using appropriate heap node */
-			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode[i], node);
+			procInfo = pairingheap_container(WaitLSNProcInfo, heapNode, node);
 
 			if (XLogRecPtrIsValid(currentLSN) && procInfo->waitLSN > currentLSN)
 				break;
@@ -238,7 +240,7 @@ wakeupWaiters(WaitLSNType lsnType, XLogRecPtr currentLSN)
 			(void) pairingheap_remove_first(&waitLSNState->waitersHeap[i]);
 
 			/* Update appropriate flag */
-			procInfo->inHeap[i] = false;
+			procInfo->inHeap = false;
 
 			if (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE)
 				break;
@@ -289,20 +291,14 @@ WaitLSNCleanup(void)
 {
 	if (waitLSNState)
 	{
-		int			i;
-
 		/*
-		 * We do a fast-path check of the heap flags without the lock.  These
-		 * flags are set to true only by the process itself.  So, it's only
+		 * We do a fast-path check of the inHeap flag without the lock.  This
+		 * flag is set to true only by the process itself.  So, it's only
 		 * possible to get a false positive.  But that will be eliminated by a
 		 * recheck inside deleteLSNWaiter().
 		 */
-
-		for (i = 0; i < (int) WAIT_LSN_TYPE_COUNT; i++)
-		{
-			if (waitLSNState->procInfos[MyProcNumber].inHeap[i])
-				deleteLSNWaiter((WaitLSNType) i);
-		}
+		if (waitLSNState->procInfos[MyProcNumber].inHeap)
+			deleteLSNWaiter(waitLSNState->procInfos[MyProcNumber].lsnType);
 	}
 }
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index f43e481c3b9..e607441d618 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -50,14 +50,20 @@ typedef struct WaitLSNProcInfo
 	/* LSN, which this process is waiting for */
 	XLogRecPtr	waitLSN;
 
+	/* The type of LSN to wait */
+	WaitLSNType lsnType;
+
 	/* Process to wake up once the waitLSN is reached */
 	ProcNumber	procno;
 
-	/* Heap membership flags for LSN types */
-	bool		inHeap[WAIT_LSN_TYPE_COUNT];
+	/*
+	 * Heap membership flag.  A process can wait for only one LSN type at a
+	 * time, so a single flag suffices (tracked by the lsnType field).
+	 */
+	bool		inHeap;
 
-	/* Heap nodes for LSN types */
-	pairingheap_node heapNode[WAIT_LSN_TYPE_COUNT];
+	/* Pairing heap node for the waiters' heap (one per process) */
+	pairingheap_node heapNode;
 } WaitLSNProcInfo;
 
 /*
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-05 14:03                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-07 22:02                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 12:08                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-16 13:30                                                                       ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-16 13:30 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi!

On Sun, Nov 16, 2025 at 8:09 PM Alexander Korotkov <[email protected]> wrote:
>
> On Sat, Nov 8, 2025 at 12:02 AM Alexander Korotkov <[email protected]> wrote:
> > On Wed, Nov 5, 2025 at 4:03 PM Xuneng Zhou <[email protected]> wrote:
> > > On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
> > > > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > > > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > > > > On 2025-Nov-03, Alexander Korotkov wrote:
> > > > > >
> > > > > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > > > > push this if no objections.
> > > > > >
> > > > > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > > > >
> > > > > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > > > independently, it's still different type of code that's affected. And the
> > > > > patches are each big enough to make that worthwhile for easier review.
> > > >
> > > > Thank you for the feedback, pushed.
> > >
> > > Thanks for pushing them!
> > >
> > > > > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > > > > case of wait_for_catchup() use this facility, instead of polling...
> > > >
> > > > The draft patch for that is attached.  WAIT FOR doesn't handle all the
> > > > possible use cases of wait_for_catchup(), but I've added usage when
> > > > it's appropriate.
> > >
> > > Interesting, could this approach be extended to the flush and other
> > > modes as well? I might need to spend some time to understand it before
> > > I can provide a meaningful review.
> >
> > I think we might end up extending WaitLSNType enum.  However, I hate
> > inHeap and heapNode arrays growing in WaitLSNProcInfo as they are
> > allocated per process.  I found that we could optimize WaitLSNProcInfo
> > struct turning them into simple variables because a single process can
> > wait only for a single LSN at a time.  Please, check the attached
> > patch.
>
> Here is the updated patch integrating minor corrections provided by
> Xuneng Zhou off-list.  I'm going to push this if no objections.
>
> ------
> Regards,
> Alexander Korotkov
> Supabase

LGTM. Thanks.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-12 07:19                                                                 ` Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-12 07:19 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
>
> Hi!
>
> On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > On 2025-Nov-03, Alexander Korotkov wrote:
> > >
> > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > push this if no objections.
> > >
> > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> >
> > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > independently, it's still different type of code that's affected. And the
> > patches are each big enough to make that worthwhile for easier review.
>
> Thank you for the feedback, pushed.
>
> > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > case of wait_for_catchup() use this facility, instead of polling...
>
> The draft patch for that is attached.  WAIT FOR doesn't handle all the
> possible use cases of wait_for_catchup(), but I've added usage when
> it's appropriate.
>
> ------
> Regards,
> Alexander Korotkov
> Supabase

I tested the patch using make check-world, and it worked well. I also
made a few adjustments:

- Added an unconditional chomp($isrecovery) after querying
pg_is_in_recovery() to prevent newline mismatches when $target_lsn is
accidently defined.
- Added chomp($output) to normalize the result from WAIT FOR LSN
before comparison.

At the moment, the WAIT FOR LSN command supports only the replay mode.
If we intend to extend its functionality more broadly, one option is
to add a mode option or something similar. Are users expected to wait
for flush(or others) completion in such cases? If not, and the TAP
test is the only intended use, this approach might be a bit of an
overkill.

--
Best,
Xuneng


Attachments:

  [application/octet-stream] v2-0001-Use-WAIT-FOR-LSN-in.patch (2.4K, ../../CABPTF7Wd=JEPX560J3V=6pnT5bB6kQiHq5DPH=zQzG2dM6zQhg@mail.gmail.com/2-v2-0001-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From e24a00603080d476087b8e327284d849f72d86a8 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 12 Nov 2025 13:32:05 +0800
Subject: [PATCH v2] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 32 +++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 35413f14019..41784553d4b 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3328,6 +3328,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3340,9 +3343,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3360,6 +3360,32 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when appropriate
+	if (($mode eq 'replay') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for other modes or when WAIT FOR LSN is not applicable
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-16 12:36                                                                   ` Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-16 12:36 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Nov 12, 2025 at 9:20 AM Xuneng Zhou <[email protected]> wrote:
> On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
> > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > > On 2025-Nov-03, Alexander Korotkov wrote:
> > > >
> > > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > > push this if no objections.
> > > >
> > > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > >
> > > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > independently, it's still different type of code that's affected. And the
> > > patches are each big enough to make that worthwhile for easier review.
> >
> > Thank you for the feedback, pushed.
> >
> > > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > > case of wait_for_catchup() use this facility, instead of polling...
> >
> > The draft patch for that is attached.  WAIT FOR doesn't handle all the
> > possible use cases of wait_for_catchup(), but I've added usage when
> > it's appropriate.
>
> I tested the patch using make check-world, and it worked well. I also
> made a few adjustments:
>
> - Added an unconditional chomp($isrecovery) after querying
> pg_is_in_recovery() to prevent newline mismatches when $target_lsn is
> accidently defined.
> - Added chomp($output) to normalize the result from WAIT FOR LSN
> before comparison.
>
> At the moment, the WAIT FOR LSN command supports only the replay mode.
> If we intend to extend its functionality more broadly, one option is
> to add a mode option or something similar. Are users expected to wait
> for flush(or others) completion in such cases? If not, and the TAP
> test is the only intended use, this approach might be a bit of an
> overkill.

I would say that adding mode parameter seems to be a pretty natural
extension of what we have at the moment.  I can imagine some
clustering solution can use it to wait for certain transaction to be
flushed at the replica (without delaying the commit at the primary).

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-16 14:01                                                                     ` Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-16 14:01 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi!

On Sun, Nov 16, 2025 at 8:37 PM Alexander Korotkov <[email protected]> wrote:
>
> On Wed, Nov 12, 2025 at 9:20 AM Xuneng Zhou <[email protected]> wrote:
> > On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
> > > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > > > On 2025-Nov-03, Alexander Korotkov wrote:
> > > > >
> > > > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > > > push this if no objections.
> > > > >
> > > > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > > >
> > > > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > > independently, it's still different type of code that's affected. And the
> > > > patches are each big enough to make that worthwhile for easier review.
> > >
> > > Thank you for the feedback, pushed.
> > >
> > > > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > > > case of wait_for_catchup() use this facility, instead of polling...
> > >
> > > The draft patch for that is attached.  WAIT FOR doesn't handle all the
> > > possible use cases of wait_for_catchup(), but I've added usage when
> > > it's appropriate.
> >
> > I tested the patch using make check-world, and it worked well. I also
> > made a few adjustments:
> >
> > - Added an unconditional chomp($isrecovery) after querying
> > pg_is_in_recovery() to prevent newline mismatches when $target_lsn is
> > accidently defined.
> > - Added chomp($output) to normalize the result from WAIT FOR LSN
> > before comparison.
> >
> > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > If we intend to extend its functionality more broadly, one option is
> > to add a mode option or something similar. Are users expected to wait
> > for flush(or others) completion in such cases? If not, and the TAP
> > test is the only intended use, this approach might be a bit of an
> > overkill.
>
> I would say that adding mode parameter seems to be a pretty natural
> extension of what we have at the moment.  I can imagine some
> clustering solution can use it to wait for certain transaction to be
> flushed at the replica (without delaying the commit at the primary).
>
> ------
> Regards,
> Alexander Korotkov
> Supabase

Makes sense. I'll play with it and try to prepare a follow-up patch.

--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-20 12:43                                                                       ` Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-20 12:43 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander, Hackers,

On Sun, Nov 16, 2025 at 10:01 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi!
>
> On Sun, Nov 16, 2025 at 8:37 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Wed, Nov 12, 2025 at 9:20 AM Xuneng Zhou <[email protected]> wrote:
> > > On Wed, Nov 5, 2025 at 5:51 PM Alexander Korotkov <[email protected]> wrote:
> > > > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > > > On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > > > > On 2025-Nov-03, Alexander Korotkov wrote:
> > > > > >
> > > > > > > I'd like to give this subject another chance for pg19.  I'm going to
> > > > > > > push this if no objections.
> > > > > >
> > > > > > Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > > > >
> > > > > FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > > > independently, it's still different type of code that's affected. And the
> > > > > patches are each big enough to make that worthwhile for easier review.
> > > >
> > > > Thank you for the feedback, pushed.
> > > >
> > > > > One thing that'd be nice to do once we have WAIT FOR is to make the common
> > > > > case of wait_for_catchup() use this facility, instead of polling...
> > > >
> > > > The draft patch for that is attached.  WAIT FOR doesn't handle all the
> > > > possible use cases of wait_for_catchup(), but I've added usage when
> > > > it's appropriate.
> > >
> > > I tested the patch using make check-world, and it worked well. I also
> > > made a few adjustments:
> > >
> > > - Added an unconditional chomp($isrecovery) after querying
> > > pg_is_in_recovery() to prevent newline mismatches when $target_lsn is
> > > accidently defined.
> > > - Added chomp($output) to normalize the result from WAIT FOR LSN
> > > before comparison.
> > >
> > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > If we intend to extend its functionality more broadly, one option is
> > > to add a mode option or something similar. Are users expected to wait
> > > for flush(or others) completion in such cases? If not, and the TAP
> > > test is the only intended use, this approach might be a bit of an
> > > overkill.
> >
> > I would say that adding mode parameter seems to be a pretty natural
> > extension of what we have at the moment.  I can imagine some
> > clustering solution can use it to wait for certain transaction to be
> > flushed at the replica (without delaying the commit at the primary).
> >
> > ------
> > Regards,
> > Alexander Korotkov
> > Supabase
>
> Makes sense. I'll play with it and try to prepare a follow-up patch.
>
> --
> Best,
> Xuneng

In terms of extending the functionality of the command, I see two
possible approaches here. One is to keep mode as a mandatory keyword,
and the other is to introduce it as an option in the WITH clause.

Syntax Option A: Mode in the WITH Clause

WAIT FOR LSN '0/12345' WITH (mode = 'replay');
WAIT FOR LSN '0/12345' WITH (mode = 'flush');
WAIT FOR LSN '0/12345' WITH (mode = 'write');

With this option, we can keep "replay" as the default mode. That means
existing TAP tests won’t need to be refactored unless they explicitly
want a different mode.

Syntax Option B: Mode as Part of the Main Command

WAIT FOR LSN '0/12345' MODE 'replay';
WAIT FOR LSN '0/12345' MODE 'flush';
WAIT FOR LSN '0/12345' MODE 'write';

Or a more concise variant using keywords:

WAIT FOR LSN '0/12345' REPLAY;
WAIT FOR LSN '0/12345' FLUSH;
WAIT FOR LSN '0/12345' WRITE;

This option produces a cleaner syntax if the intent is simply to wait
for a particular LSN type, without specifying additional options like
timeout or no_throw.

I don’t have a clear preference among them. I’d be interested to hear
what you or others think is the better direction.


--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-25 11:51                                                                         ` Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-25 11:51 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi!

> > > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > > If we intend to extend its functionality more broadly, one option is
> > > > to add a mode option or something similar. Are users expected to wait
> > > > for flush(or others) completion in such cases? If not, and the TAP
> > > > test is the only intended use, this approach might be a bit of an
> > > > overkill.
> > >
> > > I would say that adding mode parameter seems to be a pretty natural
> > > extension of what we have at the moment.  I can imagine some
> > > clustering solution can use it to wait for certain transaction to be
> > > flushed at the replica (without delaying the commit at the primary).
> > >
> > > ------
> > > Regards,
> > > Alexander Korotkov
> > > Supabase
> >
> > Makes sense. I'll play with it and try to prepare a follow-up patch.
> >
> > --
> > Best,
> > Xuneng
>
> In terms of extending the functionality of the command, I see two
> possible approaches here. One is to keep mode as a mandatory keyword,
> and the other is to introduce it as an option in the WITH clause.
>
> Syntax Option A: Mode in the WITH Clause
>
> WAIT FOR LSN '0/12345' WITH (mode = 'replay');
> WAIT FOR LSN '0/12345' WITH (mode = 'flush');
> WAIT FOR LSN '0/12345' WITH (mode = 'write');
>
> With this option, we can keep "replay" as the default mode. That means
> existing TAP tests won’t need to be refactored unless they explicitly
> want a different mode.
>
> Syntax Option B: Mode as Part of the Main Command
>
> WAIT FOR LSN '0/12345' MODE 'replay';
> WAIT FOR LSN '0/12345' MODE 'flush';
> WAIT FOR LSN '0/12345' MODE 'write';
>
> Or a more concise variant using keywords:
>
> WAIT FOR LSN '0/12345' REPLAY;
> WAIT FOR LSN '0/12345' FLUSH;
> WAIT FOR LSN '0/12345' WRITE;
>
> This option produces a cleaner syntax if the intent is simply to wait
> for a particular LSN type, without specifying additional options like
> timeout or no_throw.
>
> I don’t have a clear preference among them. I’d be interested to hear
> what you or others think is the better direction.
>

I've implemented a patch that adds MODE support to WAIT FOR LSN

The new grammar looks like:

——
WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]
——

Two modes added: flush and write

Design decisions:

1. MODE as a separate keyword (not in WITH clause) - This follows the
pattern used by LOCK command. It also makes the common case more
concise.

2. REPLAY as the default - When MODE is not specified, it defaults to REPLAY.

3. Keywords rather than strings - Using `MODE WRITE` rather than `MODE 'write'`

The patch set includes:
-------
0001 - Extend xlogwait infrastructure with write and flush wait types

Adds WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH to WaitLSNType enum,
along with corresponding wait events and pairing heaps. Introduces
GetCurrentLSNForWaitType() to retrieve the appropriate LSN based on
wait type, and adds wakeup calls in walreceiver for write/flush
events.

-------
0002 - Add pg_last_wal_write_lsn() SQL function

Adds a new SQL function that returns the current WAL write position on
a standby using GetWalRcvWriteRecPtr(). This complements existing
pg_last_wal_receive_lsn() (flush) and pg_last_wal_replay_lsn()
functions, enabling verification of WAIT FOR LSN MODE WRITE in TAP
tests.

-------
0003 - Add MODE parameter to WAIT FOR LSN command

Extends the parser and executor to support the optional MODE
parameter. Updates documentation with new syntax and mode
descriptions. Adds TAP tests covering all three modes including
mixed-mode concurrent waiters.

-------
0004 - Add tab completion for WAIT FOR LSN MODE parameter

Adds psql tab completion support: completes MODE after LSN value,
completes REPLAY/WRITE/FLUSH after MODE keyword, and completes WITH
after mode selection.

-------
0005 - Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()

Replaces polling-based wait_for_catchup() with WAIT FOR LSN when the
target is a standby in recovery, improving test efficiency by avoiding
repeated queries.

The WRITE and FLUSH modes enable scenarios where applications need to
ensure WAL has been received or persisted on the standby without
waiting for replay to complete.

Feedback welcome.


--
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0002-Add-pg_last_wal_write_lsn-SQL-function.patch (3.4K, ../../CABPTF7W5mZ4MOBdfU6ftOXdX7mYR=yY-PNTEnvrObz4JRLccnQ@mail.gmail.com/2-v1-0002-Add-pg_last_wal_write_lsn-SQL-function.patch)
  download | inline diff:
From 7227bca84a9233fb2d7c130294511d48d8458e2f Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:07:52 +0800
Subject: [PATCH v1 2/5] Add pg_last_wal_write_lsn() SQL function

Returns the current WAL write position on a standby server using
GetWalRcvWriteRecPtr(). This enables verification of WAIT FOR LSN MODE WRITE
and operational monitoring of standby WAL write progress.
---
 doc/src/sgml/func/func-admin.sgml      | 19 +++++++++++++++++++
 src/backend/access/transam/xlogfuncs.c | 19 +++++++++++++++++++
 src/include/catalog/pg_proc.dat        |  4 ++++
 3 files changed, 42 insertions(+)

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 1b465bc8ba7..ed4e77d12ba 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -688,6 +688,25 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_last_wal_write_lsn</primary>
+        </indexterm>
+        <function>pg_last_wal_write_lsn</function> ()
+        <returnvalue>pg_lsn</returnvalue>
+       </para>
+       <para>
+        Returns the last write-ahead log location that has been received and
+        written to disk by streaming replication, but not necessarily synced.
+        While streaming replication is in progress this will increase
+        monotonically. If recovery has completed then this will remain static
+        at the location of the last WAL record written during recovery. If
+        streaming replication is disabled, or if it has not yet started, the
+        function returns <literal>NULL</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 3e45fce43ed..46cd4a7ce2f 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -347,6 +347,25 @@ pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
 	PG_RETURN_LSN(recptr);
 }
 
+/*
+ * Report the last WAL write location (same format as pg_backup_start etc)
+ *
+ * This is useful for determining how much of WAL has been received and
+ * written to disk by walreceiver, but not necessarily synced/flushed.
+ */
+Datum
+pg_last_wal_write_lsn(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr	recptr;
+
+	recptr = GetWalRcvWriteRecPtr();
+
+	if (!XLogRecPtrIsValid(recptr))
+		PG_RETURN_NULL();
+
+	PG_RETURN_LSN(recptr);
+}
+
 /*
  * Report the last WAL replay location (same format as pg_backup_start etc)
  *
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66431940700..fcb674c05b3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6782,6 +6782,10 @@
   proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
   prorettype => 'pg_lsn', proargtypes => '',
   prosrc => 'pg_last_wal_receive_lsn' },
+{ oid => '6434', descr => 'current wal write location',
+  proname => 'pg_last_wal_write_lsn', provolatile => 'v',
+  prorettype => 'pg_lsn', proargtypes => '',
+  prosrc => 'pg_last_wal_write_lsn' },
 { oid => '3821', descr => 'last wal replay location',
   proname => 'pg_last_wal_replay_lsn', provolatile => 'v',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.51.0



  [application/octet-stream] v1-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (8.3K, ../../CABPTF7W5mZ4MOBdfU6ftOXdX7mYR=yY-PNTEnvrObz4JRLccnQ@mail.gmail.com/3-v1-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From ca10a52bd7a835b2873268236a4553fc911e2de3 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 17:58:28 +0800
Subject: [PATCH v1 1/5] Extend xlogwait infrastructure with write and flush 
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH_STANDBY to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlogwait.c         | 79 ++++++++++++++-----
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 |  7 +-
 3 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 98aa5f1e4a2..86709e0df63 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,43 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_FLUSH_PRIMARY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_FLUSH_STANDBY:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_FLUSH_PRIMARY:
+			return GetFlushRecPtr(NULL);
+
+		default:
+			elog(ERROR, "invalid LSN wait type: %d", lsnType);
+			return InvalidXLogRecPtr;	/* keep compiler quiet */
+	}
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +384,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_FLUSH_PRIMARY && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +417,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c1ac71ff7f2..fbcdb92dcfb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index e607441d618..64a2fb02eac 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -36,8 +36,10 @@ typedef enum
 typedef enum WaitLSNType
 {
 	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
-	WAIT_LSN_TYPE_COUNT = 2
+	WAIT_LSN_TYPE_FLUSH_STANDBY = 1,	/* Waiting for flush on standby */
+	WAIT_LSN_TYPE_WRITE = 2,	/* Waiting for write on standby */
+	WAIT_LSN_TYPE_FLUSH_PRIMARY = 3,	/* Waiting for flush on primary */
+	WAIT_LSN_TYPE_COUNT = 4
 } WaitLSNType;
 
 /*
@@ -96,6 +98,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v1-0005-Use-WAIT-FOR-LSN-in.patch (3.1K, ../../CABPTF7W5mZ4MOBdfU6ftOXdX7mYR=yY-PNTEnvrObz4JRLccnQ@mail.gmail.com/4-v1-0005-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From 6229917d4802a82bb63ac41ec32a7ca357701c67 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:20:18 +0800
Subject: [PATCH v1 5/5] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 35 ++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 35413f14019..b2a4e2e2253 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3328,6 +3328,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3340,9 +3343,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3360,6 +3360,35 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		# Map mode names to WAIT FOR LSN MODE values (uppercase)
+		my $wait_mode = uc($mode);
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' MODE ${wait_mode} WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v1-0003-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch (35.3K, ../../CABPTF7W5mZ4MOBdfU6ftOXdX7mYR=yY-PNTEnvrObz4JRLccnQ@mail.gmail.com/5-v1-0003-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 071f67d1fae98e397c071dce0b9993b3be0c0e9f Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:11:54 +0800
Subject: [PATCH v1 3/5] Add MODE parameter to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE parameter that
specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]

- REPLAY (default): Wait for WAL to be replayed to the specified LSN
- WRITE: Wait for WAL to be written (received) to the specified LSN
- FLUSH: Wait for WAL to be flushed to disk at the specified LSN

The default mode is REPLAY, matching the original behavior when MODE
is not specified. This follows the pattern used by LOCK command where
the mode parameter is optional with a sensible default.

The WRITE and FLUSH modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and refactoring
  of existing WAIT FOR command documentation
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for WRITE/FLUSH waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 184 ++++++++++++++++------
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  59 +++++--
 src/backend/parser/gram.y               |  21 ++-
 src/backend/replication/walreceiver.c   |  19 +++
 src/include/nodes/parsenodes.h          |  16 ++
 src/include/parser/kwlist.h             |   2 +
 src/test/recovery/t/049_wait_for_lsn.pl | 201 +++++++++++++++++++++---
 8 files changed, 422 insertions(+), 86 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..efd851149c0 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,12 +16,13 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ MODE { REPLAY | FLUSH | WRITE } ]
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
@@ -34,20 +35,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -64,6 +67,53 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      Specifies the type of LSN processing to wait for. If not specified,
+      the default is <literal>REPLAY</literal>. The valid modes are:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term><literal>REPLAY</literal></term>
+       <listitem>
+        <para>
+         Wait for the LSN to be replayed (applied to the database).
+         After successful completion, <function>pg_last_wal_replay_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FLUSH</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and flushed to durable storage on the replica. This
+         provides a durability guarantee without waiting for the WAL
+         to be applied.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>WRITE</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and written to the operating system on the replica.
+         This is faster than <literal>FLUSH</literal> but provides weaker
+         durability guarantees since the data may still be in OS buffers.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
     <listitem>
@@ -135,9 +185,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +201,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +236,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -198,9 +259,9 @@ pg_current_wal_insert_lsn
    changes made on primary should be guaranteed to be visible on replica.
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20';
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY;
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,21 +272,46 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+postgres=# WAIT FOR LSN '0/306EE20' MODE FLUSH;
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE WRITE WITH (TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '0.1s');
 ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
 </programlisting>
   </para>
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 22d0a2e8c3a..a4c7a7c2b38 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6240,10 +6240,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..73876ca5c7c 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,12 +29,29 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_REPLAY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = {"flush", "flushed"},
+	[WAIT_LSN_TYPE_WRITE] = {"write", "written"},
+};
+
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType;
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
@@ -41,6 +59,16 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
 
+	/*
+	 * Convert parse-time WaitLSNMode to runtime WaitLSNType. Values are
+	 * designed to match, so a simple cast is safe.
+	 */
+	lsnType = (WaitLSNType) stmt->mode;
+
+	/* Validate mode value (should never fail if grammar is correct) */
+	Assert(lsnType >= WAIT_LSN_TYPE_REPLAY &&
+		   lsnType < WAIT_LSN_TYPE_FLUSH_PRIMARY);
+
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
@@ -107,8 +135,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +168,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +182,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +201,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..57b3e91893c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -640,6 +640,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
+%type <ival>	opt_wait_lsn_mode
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <boolean> opt_unique_null_treatment
@@ -729,7 +730,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
 	EXPRESSION EXTENSION EXTERNAL EXTRACT
 
-	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
+	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FLUSH FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -770,7 +771,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLAY REPLICA
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -16489,15 +16490,23 @@ xml_passing_mech:
  *****************************************************************************/
 
 WaitStmt:
-			WAIT FOR LSN_P Sconst opt_wait_with_clause
+			WAIT FOR LSN_P Sconst opt_wait_lsn_mode opt_wait_with_clause
 				{
 					WaitStmt *n = makeNode(WaitStmt);
 					n->lsn_literal = $4;
-					n->options = $5;
+					n->mode = $5;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
 			;
 
+opt_wait_lsn_mode:
+			MODE REPLAY			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			| MODE FLUSH		{ $$ = WAIT_LSN_MODE_FLUSH; }
+			| MODE WRITE		{ $$ = WAIT_LSN_MODE_WRITE; }
+			| /*EMPTY*/			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			;
+
 opt_wait_with_clause:
 			WITH '(' utility_option_list ')'		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -17937,6 +17946,7 @@ unreserved_keyword:
 			| FILTER
 			| FINALIZE
 			| FIRST_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FORMAT
@@ -18071,6 +18081,7 @@ unreserved_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESPECT_P
@@ -18524,6 +18535,7 @@ bare_label_keyword:
 			| FINALIZE
 			| FIRST_P
 			| FLOAT_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FOREIGN
@@ -18706,6 +18718,7 @@ bare_label_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESTART
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 4217fc54e2e..818049599ed 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_FLUSH_STANDBY])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d14294a4ece..68dc49dc2da 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,10 +4385,26 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+/*
+ * WaitLSNMode - MODE parameter for WAIT FOR command
+ *
+ * These values are defined to match WaitLSNType in access/xlogwait.h
+ * for efficient conversion without overhead. The values must be kept
+ * in sync with WaitLSNType.
+ */
+typedef enum WaitLSNMode
+{
+	WAIT_LSN_MODE_REPLAY = 0,	/* Wait for LSN replay on standby */
+	WAIT_LSN_MODE_FLUSH = 1,	/* Wait for LSN flush to disk on standby */
+	WAIT_LSN_MODE_WRITE = 2		/* Wait for LSN write to WAL buffers on
+								 * standby */
+} WaitLSNMode;
+
 typedef struct WaitStmt
 {
 	NodeTag		type;
 	char	   *lsn_literal;	/* LSN string from grammar */
+	WaitLSNMode mode;			/* Wait mode: REPLAY/FLUSH/WRITE */
 	List	   *options;		/* List of DefElem nodes */
 } WaitStmt;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..7ad8b11b725 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -176,6 +176,7 @@ PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("flush", FLUSH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -378,6 +379,7 @@ PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("replay", REPLAY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD, AS_LABEL)
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..e579b98f019 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -62,7 +62,40 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$node_standby->safe_psql('postgres',
+	"WAIT FOR LSN '${lsn_write}' MODE WRITE WITH (timeout '1d');");
+
+# Verify via pg_stat_replication that standby reported the write
+my $standby_write_lsn = $node_primary->safe_psql(
+	'postgres', qq[
+	SELECT write_lsn FROM pg_stat_replication
+	WHERE application_name = 'standby';
+]);
+
+ok( $node_primary->safe_psql('postgres',
+		"SELECT '${standby_write_lsn}'::pg_lsn >= '${lsn_write}'::pg_lsn") eq
+	  't',
+	"standby wrote WAL up to target LSN after WAIT FOR MODE WRITE");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' MODE FLUSH WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR MODE FLUSH");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +121,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +158,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +241,7 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -239,7 +272,7 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->query_until(
 		qr/start/, qq[
 		\\echo start
-		WAIT FOR LSN '${lsn}';
+		WAIT FOR LSN '${lsn}' MODE REPLAY;
 		SELECT log_count(${i});
 	]);
 }
@@ -251,23 +284,138 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
+
+# 7b. Check the scenario of multiple WRITE waiters.
+my @write_sessions;
+my @write_lsns;
+for (my $i = 0; $i < 3; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' MODE WRITE WITH (timeout '1d');
+	]);
+}
+
+# Wait for all WAIT FOR LSN commands to complete
+for (my $i = 0; $i < 3; $i++)
+{
+	$write_sessions[$i]->{run}->finish;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '$write_lsns[2]'::pg_lsn);");
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+my @flush_sessions;
+my @flush_lsns;
+for (my $i = 0; $i < 3; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' MODE FLUSH WITH (timeout '1d');
+	]);
+}
+
+# Wait for all WAIT FOR LSN commands to complete
+for (my $i = 0; $i < 3; $i++)
+{
+	$flush_sessions[$i]->{run}->finish;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[2]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  When all complete, we verify that the replay LSN
+# (the slowest to advance due to recovery_min_apply_delay) has reached the
+# target.  Since REPLAY waiters block until replay completes, and WRITE/FLUSH
+# complete earlier, successful completion of all sessions proves proper
+# coordination.
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my @mixed_sessions;
+my @mixed_modes = ('REPLAY', 'WRITE', 'FLUSH');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' MODE $mixed_modes[$i % 3] WITH (timeout '1d');
+	]);
+}
+
+# Resume replay so REPLAY waiters can complete
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+
+# Wait for all sessions to complete - this blocks until WAIT FOR LSN returns
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i]->{run}->finish;
+}
+
+# Verify: if all waiters completed, then the slowest (REPLAY) must have
+# reached the target LSN, which implies WRITE and FLUSH also succeeded
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"mixed mode waiters: all modes completed, replay reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('REPLAY', 'WRITE', 'FLUSH');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' MODE $wait_modes[$i];
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,17 +425,23 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
+
+# Wait for at least one "recovery is not in progress" error to appear
 $node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Verify all three sessions got the error by checking the log contains
+# the error message at least three times (from the promotion point)
+my $log_contents = slurp_file($node_standby->logfile, $log_offset);
+my $error_count = () = $log_contents =~ /recovery is not in progress/g;
+ok($error_count >= 3, 'promotion interrupted all wait modes');
 
-$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}' MODE REPLAY;");
 
 ok(1, 'wait for already replayed LSN exits immediately even after promotion');
 
 $output = $node_standby->safe_psql(
 	'postgres', qq[
-	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+	WAIT FOR LSN '${lsn4}' MODE REPLAY WITH (timeout '10ms', no_throw);]);
 ok($output eq "not in recovery",
 	"WAIT FOR returns correct status after standby promotion");
 
@@ -295,8 +449,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



  [application/octet-stream] v1-0004-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch (3.2K, ../../CABPTF7W5mZ4MOBdfU6ftOXdX7mYR=yY-PNTEnvrObz4JRLccnQ@mail.gmail.com/6-v1-0004-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch)
  download | inline diff:
From 1bb41bdd83b37f9ef7237095a368ea21e589d262 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:18:06 +0800
Subject: [PATCH v1 4/5] Add tab completion for WAIT FOR LSN MODE parameter

Update psql tab completion to support the optional MODE parameter in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords since MODE defaults to REPLAY.
---
 src/bin/psql/tab-complete.in.c | 39 ++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 20d7a65c614..fcb9f19faef 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5313,10 +5313,11 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /*
- * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
+ * WAIT FOR LSN '<lsn>' [ MODE { REPLAY | FLUSH | WRITE } ] [ WITH ( option [, ...] ) ]
  * where option can be:
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * MODE defaults to REPLAY if not specified.
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5325,25 +5326,41 @@ match_previous_words(int pattern_id,
 	else if (Matches("WAIT", "FOR", "LSN"))
 		/* No completion for LSN value - user must provide manually */
 		;
+
+	/*
+	 * After LSN value, offer MODE (optional) or WITH, since MODE defaults to
+	 * REPLAY
+	 */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny))
+		COMPLETE_WITH("MODE", "WITH");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE"))
+		COMPLETE_WITH("REPLAY", "FLUSH", "WRITE");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny))
 		COMPLETE_WITH("WITH");
+	/* WITH directly after LSN (using default REPLAY mode) */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list (both with and without explicit MODE).
+	 * This fires when we're in an unfinished parenthesized option list.
+	 * get_previous_words treats a completed parenthesized option list as one
+	 * word, so the above test is correct. timeout takes a string value,
+	 * no_throw takes no value. We don't offer completions for these values.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
 			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+	}
+	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*") &&
+			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*)"))
+	{
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("timeout", "no_throw");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-01 04:33                                                                           ` Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-01 04:33 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi hackers,

On Tue, Nov 25, 2025 at 7:51 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi!
>
> > > > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > > > If we intend to extend its functionality more broadly, one option is
> > > > > to add a mode option or something similar. Are users expected to wait
> > > > > for flush(or others) completion in such cases? If not, and the TAP
> > > > > test is the only intended use, this approach might be a bit of an
> > > > > overkill.
> > > >
> > > > I would say that adding mode parameter seems to be a pretty natural
> > > > extension of what we have at the moment.  I can imagine some
> > > > clustering solution can use it to wait for certain transaction to be
> > > > flushed at the replica (without delaying the commit at the primary).
> > > >
> > > > ------
> > > > Regards,
> > > > Alexander Korotkov
> > > > Supabase
> > >
> > > Makes sense. I'll play with it and try to prepare a follow-up patch.
> > >
> > > --
> > > Best,
> > > Xuneng
> >
> > In terms of extending the functionality of the command, I see two
> > possible approaches here. One is to keep mode as a mandatory keyword,
> > and the other is to introduce it as an option in the WITH clause.
> >
> > Syntax Option A: Mode in the WITH Clause
> >
> > WAIT FOR LSN '0/12345' WITH (mode = 'replay');
> > WAIT FOR LSN '0/12345' WITH (mode = 'flush');
> > WAIT FOR LSN '0/12345' WITH (mode = 'write');
> >
> > With this option, we can keep "replay" as the default mode. That means
> > existing TAP tests won’t need to be refactored unless they explicitly
> > want a different mode.
> >
> > Syntax Option B: Mode as Part of the Main Command
> >
> > WAIT FOR LSN '0/12345' MODE 'replay';
> > WAIT FOR LSN '0/12345' MODE 'flush';
> > WAIT FOR LSN '0/12345' MODE 'write';
> >
> > Or a more concise variant using keywords:
> >
> > WAIT FOR LSN '0/12345' REPLAY;
> > WAIT FOR LSN '0/12345' FLUSH;
> > WAIT FOR LSN '0/12345' WRITE;
> >
> > This option produces a cleaner syntax if the intent is simply to wait
> > for a particular LSN type, without specifying additional options like
> > timeout or no_throw.
> >
> > I don’t have a clear preference among them. I’d be interested to hear
> > what you or others think is the better direction.
> >
>
> I've implemented a patch that adds MODE support to WAIT FOR LSN
>
> The new grammar looks like:
>
> ——
> WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]
> ——
>
> Two modes added: flush and write
>
> Design decisions:
>
> 1. MODE as a separate keyword (not in WITH clause) - This follows the
> pattern used by LOCK command. It also makes the common case more
> concise.
>
> 2. REPLAY as the default - When MODE is not specified, it defaults to REPLAY.
>
> 3. Keywords rather than strings - Using `MODE WRITE` rather than `MODE 'write'`
>
> The patch set includes:
> -------
> 0001 - Extend xlogwait infrastructure with write and flush wait types
>
> Adds WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH to WaitLSNType enum,
> along with corresponding wait events and pairing heaps. Introduces
> GetCurrentLSNForWaitType() to retrieve the appropriate LSN based on
> wait type, and adds wakeup calls in walreceiver for write/flush
> events.
>
> -------
> 0002 - Add pg_last_wal_write_lsn() SQL function
>
> Adds a new SQL function that returns the current WAL write position on
> a standby using GetWalRcvWriteRecPtr(). This complements existing
> pg_last_wal_receive_lsn() (flush) and pg_last_wal_replay_lsn()
> functions, enabling verification of WAIT FOR LSN MODE WRITE in TAP
> tests.
>
> -------
> 0003 - Add MODE parameter to WAIT FOR LSN command
>
> Extends the parser and executor to support the optional MODE
> parameter. Updates documentation with new syntax and mode
> descriptions. Adds TAP tests covering all three modes including
> mixed-mode concurrent waiters.
>
> -------
> 0004 - Add tab completion for WAIT FOR LSN MODE parameter
>
> Adds psql tab completion support: completes MODE after LSN value,
> completes REPLAY/WRITE/FLUSH after MODE keyword, and completes WITH
> after mode selection.
>
> -------
> 0005 - Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()
>
> Replaces polling-based wait_for_catchup() with WAIT FOR LSN when the
> target is a standby in recovery, improving test efficiency by avoiding
> repeated queries.
>
> The WRITE and FLUSH modes enable scenarios where applications need to
> ensure WAL has been received or persisted on the standby without
> waiting for replay to complete.
>
> Feedback welcome.
>

Here is the updated v2 patch set. Most of the updates are in patch 3.

Changes from v1:

Patch 1 (Extend wait types in xlogwait infra)
- Renamed enum values for consistency (WAIT_LSN_TYPE_REPLAY →
WAIT_LSN_TYPE_REPLAY_STANDBY, etc.)

Patch 2 (pg_last_wal_write_lsn):
- Clarified documentation and comment
- Improved pg_proc.dat description

Patch 3 (MODE parameter):
- Replaced direct cast with explicit switch statement for WaitLSNMode
→ WaitLSNType conversion
- Improved FLUSH/WRITE mode documentation with verification function references
- TAP tests (7b, 7c, 7d): Added walreceiver control for concurrency,
explicit blocking verification via poll_query_until, and log-based
completion verification via wait_for_log
- Fix the timing issue in wait for all three sessions to get the
errors after promotion of tap test 8.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v2-0005-Use-WAIT-FOR-LSN-in.patch (3.1K, ../../CABPTF7UJb=Buj6PHdgZLeZp0pYF4EtufkRpkeHF6XQDxcx=uSQ@mail.gmail.com/2-v2-0005-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From 02b633402db35770fd70ace6c1e6301f3dd6741b Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:20:18 +0800
Subject: [PATCH v2 5/5] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 35 ++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 35413f14019..b2a4e2e2253 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3328,6 +3328,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3340,9 +3343,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3360,6 +3360,35 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		# Map mode names to WAIT FOR LSN MODE values (uppercase)
+		my $wait_mode = uc($mode);
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' MODE ${wait_mode} WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v2-0004-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch (3.2K, ../../CABPTF7UJb=Buj6PHdgZLeZp0pYF4EtufkRpkeHF6XQDxcx=uSQ@mail.gmail.com/3-v2-0004-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch)
  download | inline diff:
From 7fcaab3d495ccc42c3f9731d1de9a15c33c01ee8 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:18:06 +0800
Subject: [PATCH v2 4/5] Add tab completion for WAIT FOR LSN MODE parameter

Update psql tab completion to support the optional MODE parameter in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords since MODE defaults to REPLAY.
---
 src/bin/psql/tab-complete.in.c | 39 ++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 20d7a65c614..fcb9f19faef 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5313,10 +5313,11 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /*
- * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
+ * WAIT FOR LSN '<lsn>' [ MODE { REPLAY | FLUSH | WRITE } ] [ WITH ( option [, ...] ) ]
  * where option can be:
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * MODE defaults to REPLAY if not specified.
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5325,25 +5326,41 @@ match_previous_words(int pattern_id,
 	else if (Matches("WAIT", "FOR", "LSN"))
 		/* No completion for LSN value - user must provide manually */
 		;
+
+	/*
+	 * After LSN value, offer MODE (optional) or WITH, since MODE defaults to
+	 * REPLAY
+	 */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny))
+		COMPLETE_WITH("MODE", "WITH");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE"))
+		COMPLETE_WITH("REPLAY", "FLUSH", "WRITE");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny))
 		COMPLETE_WITH("WITH");
+	/* WITH directly after LSN (using default REPLAY mode) */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list (both with and without explicit MODE).
+	 * This fires when we're in an unfinished parenthesized option list.
+	 * get_previous_words treats a completed parenthesized option list as one
+	 * word, so the above test is correct. timeout takes a string value,
+	 * no_throw takes no value. We don't offer completions for these values.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
 			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+	}
+	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*") &&
+			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*)"))
+	{
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("timeout", "no_throw");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v2-0002-Add-pg_last_wal_write_lsn-SQL-function.patch (3.7K, ../../CABPTF7UJb=Buj6PHdgZLeZp0pYF4EtufkRpkeHF6XQDxcx=uSQ@mail.gmail.com/4-v2-0002-Add-pg_last_wal_write_lsn-SQL-function.patch)
  download | inline diff:
From 9d22e09d378e8f6c52aa95bc4a0e1650f4621a39 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:07:52 +0800
Subject: [PATCH v2 2/5] Add pg_last_wal_write_lsn() SQL function

Returns the current WAL write position on a standby server using
GetWalRcvWriteRecPtr(). This enables verification of WAIT FOR LSN MODE WRITE
and operational monitoring of standby WAL write progress.
---
 doc/src/sgml/func/func-admin.sgml      | 22 ++++++++++++++++++++++
 src/backend/access/transam/xlogfuncs.c | 20 ++++++++++++++++++++
 src/include/catalog/pg_proc.dat        |  4 ++++
 3 files changed, 46 insertions(+)

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 1b465bc8ba7..9ff196c4be4 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -688,6 +688,28 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_last_wal_write_lsn</primary>
+        </indexterm>
+        <function>pg_last_wal_write_lsn</function> ()
+        <returnvalue>pg_lsn</returnvalue>
+       </para>
+       <para>
+        Returns the last write-ahead log location that has been received and
+        passed to the operating system by streaming replication, but not
+        necessarily synced to durable storage.  This is faster than
+        <function>pg_last_wal_receive_lsn</function> but provides weaker
+        durability guarantees since the data may still be in OS buffers.
+        While streaming replication is in progress this will increase
+        monotonically. If recovery has completed then this will remain static
+        at the location of the last WAL record written during recovery. If
+        streaming replication is disabled, or if it has not yet started, the
+        function returns <literal>NULL</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 3e45fce43ed..2797b2bf158 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -347,6 +347,26 @@ pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
 	PG_RETURN_LSN(recptr);
 }
 
+/*
+ * Report the last WAL write location (same format as pg_backup_start etc)
+ *
+ * This is useful for determining how much of WAL has been received and
+ * passed to the operating system by walreceiver.  Unlike pg_last_wal_receive_lsn,
+ * this data may still be in OS buffers and not yet synced to durable storage.
+ */
+Datum
+pg_last_wal_write_lsn(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr	recptr;
+
+	recptr = GetWalRcvWriteRecPtr();
+
+	if (!XLogRecPtrIsValid(recptr))
+		PG_RETURN_NULL();
+
+	PG_RETURN_LSN(recptr);
+}
+
 /*
  * Report the last WAL replay location (same format as pg_backup_start etc)
  *
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66431940700..478e0a8139f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6782,6 +6782,10 @@
   proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
   prorettype => 'pg_lsn', proargtypes => '',
   prosrc => 'pg_last_wal_receive_lsn' },
+{ oid => '6434', descr => 'last wal write location on standby',
+  proname => 'pg_last_wal_write_lsn', provolatile => 'v',
+  prorettype => 'pg_lsn', proargtypes => '',
+  prosrc => 'pg_last_wal_write_lsn' },
 { oid => '3821', descr => 'last wal replay location',
   proname => 'pg_last_wal_replay_lsn', provolatile => 'v',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.51.0



  [application/octet-stream] v2-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (10.6K, ../../CABPTF7UJb=Buj6PHdgZLeZp0pYF4EtufkRpkeHF6XQDxcx=uSQ@mail.gmail.com/5-v2-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From da210bfc2b62d9a38ea54b94037380144753663a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 17:58:28 +0800
Subject: [PATCH v2 1/5] Extend xlogwait infrastructure with write and flush 
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_WRITE_STANDBY and WAIT_LSN_TYPE_FLUSH_STANDBY to WaitLSNType
- Renamed enum values for consistency (WAIT_LSN_TYPE_REPLAY → WAIT_LSN_TYPE_REPLAY_STANDBY, etc.)
- Add GetCurrentLSNForWaitType() to retrieve current LSN
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 84 ++++++++++++++-----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 13 ++-
 6 files changed, 81 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 22d0a2e8c3a..4b145515269 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6243,7 +6243,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 21b8f179ba0..243c0b368a9 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1846,8 +1846,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY_STANDBY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 98aa5f1e4a2..21823acee9c 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,48 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_REPLAY_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_WRITE_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_FLUSH_PRIMARY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_REPLAY_STANDBY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_WRITE_STANDBY:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_FLUSH_STANDBY:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_FLUSH_PRIMARY:
+			return GetFlushRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_COUNT:
+			break;
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +389,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_FLUSH_PRIMARY && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +422,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..43b37095afb 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY_STANDBY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c1ac71ff7f2..fbcdb92dcfb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index e607441d618..9721a7a7195 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -35,9 +35,15 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
-	WAIT_LSN_TYPE_COUNT = 2
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_REPLAY_STANDBY = 0,
+	WAIT_LSN_TYPE_WRITE_STANDBY = 1,
+	WAIT_LSN_TYPE_FLUSH_STANDBY = 2,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_FLUSH_PRIMARY = 3,
+
+	WAIT_LSN_TYPE_COUNT = 4
 } WaitLSNType;
 
 /*
@@ -96,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v2-0003-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch (38.8K, ../../CABPTF7UJb=Buj6PHdgZLeZp0pYF4EtufkRpkeHF6XQDxcx=uSQ@mail.gmail.com/6-v2-0003-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 1367c1f3322b93190fcd4ca70ab309efd8556c77 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:11:54 +0800
Subject: [PATCH v2] Add MODE parameter to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE parameter that
specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]

- REPLAY (default): Wait for WAL to be replayed to the specified LSN
- WRITE: Wait for WAL to be written (received) to the specified LSN
- FLUSH: Wait for WAL to be flushed to disk at the specified LSN

The default mode is REPLAY, matching the original behavior when MODE
is not specified. This follows the pattern used by LOCK command where
the mode parameter is optional with a sensible default.

The WRITE and FLUSH modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and refactoring
  of existing WAIT FOR command documentation
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for WRITE/FLUSH waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 188 +++++++++++----
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  64 ++++-
 src/backend/parser/gram.y               |  21 +-
 src/backend/replication/walreceiver.c   |  19 ++
 src/include/nodes/parsenodes.h          |  11 +
 src/include/parser/kwlist.h             |   2 +
 src/test/recovery/t/049_wait_for_lsn.pl | 299 ++++++++++++++++++++++--
 8 files changed, 523 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..a5e7f6c6fe9 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,12 +16,13 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ MODE { REPLAY | FLUSH | WRITE } ]
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
@@ -34,20 +35,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -64,6 +67,57 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      Specifies the type of LSN processing to wait for. If not specified,
+      the default is <literal>REPLAY</literal>. The valid modes are:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term><literal>REPLAY</literal></term>
+       <listitem>
+        <para>
+         Wait for the LSN to be replayed (applied to the database).
+         After successful completion, <function>pg_last_wal_replay_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FLUSH</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and synced to durable storage via <function>fsync()</function>.
+         This provides a durability guarantee without waiting for the WAL
+         to be applied. After successful completion,
+         <function>pg_last_wal_receive_lsn()</function> will return a value
+         greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>WRITE</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and passed to the operating system via <function>write()</function>.
+         This is faster than <literal>FLUSH</literal> but provides weaker
+         durability guarantees since the data may still be in OS buffers.
+         After successful completion, <function>pg_last_wal_write_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
     <listitem>
@@ -135,9 +189,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +205,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +240,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -198,9 +263,9 @@ pg_current_wal_insert_lsn
    changes made on primary should be guaranteed to be visible on replica.
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20';
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY;
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,21 +276,46 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+postgres=# WAIT FOR LSN '0/306EE20' MODE FLUSH;
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE WRITE WITH (TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '0.1s');
 ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
 </programlisting>
   </para>
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4b145515269..5b2a262ff8e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6240,10 +6240,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_WRITE_STANDBY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 43b37095afb..05ad84fdb5b 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,12 +29,28 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_REPLAY_STANDBY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_WRITE_STANDBY] = {"write", "written"},
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = {"flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType;
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
@@ -45,6 +62,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
 
+	/* Convert parse-time WaitLSNMode to runtime WaitLSNType */
+	switch (stmt->mode)
+	{
+		case WAIT_LSN_MODE_REPLAY:
+			lsnType = WAIT_LSN_TYPE_REPLAY_STANDBY;
+			break;
+		case WAIT_LSN_MODE_WRITE:
+			lsnType = WAIT_LSN_TYPE_WRITE_STANDBY;
+			break;
+		case WAIT_LSN_MODE_FLUSH:
+			lsnType = WAIT_LSN_TYPE_FLUSH_STANDBY;
+			break;
+		default:
+			elog(ERROR, "unrecognized wait mode: %d", stmt->mode);
+	}
+
 	foreach_node(DefElem, defel, stmt->options)
 	{
 		if (strcmp(defel->defname, "timeout") == 0)
@@ -107,8 +140,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +173,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY_STANDBY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +187,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +206,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..57b3e91893c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -640,6 +640,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
+%type <ival>	opt_wait_lsn_mode
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <boolean> opt_unique_null_treatment
@@ -729,7 +730,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
 	EXPRESSION EXTENSION EXTERNAL EXTRACT
 
-	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
+	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FLUSH FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -770,7 +771,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLAY REPLICA
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -16489,15 +16490,23 @@ xml_passing_mech:
  *****************************************************************************/
 
 WaitStmt:
-			WAIT FOR LSN_P Sconst opt_wait_with_clause
+			WAIT FOR LSN_P Sconst opt_wait_lsn_mode opt_wait_with_clause
 				{
 					WaitStmt *n = makeNode(WaitStmt);
 					n->lsn_literal = $4;
-					n->options = $5;
+					n->mode = $5;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
 			;
 
+opt_wait_lsn_mode:
+			MODE REPLAY			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			| MODE FLUSH		{ $$ = WAIT_LSN_MODE_FLUSH; }
+			| MODE WRITE		{ $$ = WAIT_LSN_MODE_WRITE; }
+			| /*EMPTY*/			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			;
+
 opt_wait_with_clause:
 			WITH '(' utility_option_list ')'		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -17937,6 +17946,7 @@ unreserved_keyword:
 			| FILTER
 			| FINALIZE
 			| FIRST_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FORMAT
@@ -18071,6 +18081,7 @@ unreserved_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESPECT_P
@@ -18524,6 +18535,7 @@ bare_label_keyword:
 			| FINALIZE
 			| FIRST_P
 			| FLOAT_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FOREIGN
@@ -18706,6 +18718,7 @@ bare_label_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESTART
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 4217fc54e2e..be2971408e7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_WRITE_STANDBY])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_WRITE_STANDBY, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_FLUSH_STANDBY])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d14294a4ece..bbaf3242ccb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,10 +4385,21 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+/*
+ * WaitLSNMode - MODE parameter for WAIT FOR command
+ */
+typedef enum WaitLSNMode
+{
+	WAIT_LSN_MODE_REPLAY,		/* Wait for LSN replay on standby */
+	WAIT_LSN_MODE_WRITE,		/* Wait for LSN write on standby */
+	WAIT_LSN_MODE_FLUSH			/* Wait for LSN flush on standby */
+}			WaitLSNMode;
+
 typedef struct WaitStmt
 {
 	NodeTag		type;
 	char	   *lsn_literal;	/* LSN string from grammar */
+	WaitLSNMode mode;			/* Wait mode: REPLAY/FLUSH/WRITE */
 	List	   *options;		/* List of DefElem nodes */
 } WaitStmt;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..7ad8b11b725 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -176,6 +176,7 @@ PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("flush", FLUSH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -378,6 +379,7 @@ PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("replay", REPLAY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD, AS_LABEL)
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..6c9a463775b 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -62,7 +62,34 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' MODE WRITE WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '${lsn_write}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR MODE WRITE");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' MODE FLUSH WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR MODE FLUSH");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +115,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +152,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +235,7 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -239,7 +266,7 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->query_until(
 		qr/start/, qq[
 		\\echo start
-		WAIT FOR LSN '${lsn}';
+		WAIT FOR LSN '${lsn}' MODE REPLAY;
 		SELECT log_count(${i});
 	]);
 }
@@ -251,23 +278,239 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
+
+# 7b. Check the scenario of multiple WRITE waiters.
+# Stop walreceiver to ensure waiters actually block.
+my $orig_conninfo = $node_standby->safe_psql('postgres',
+	"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+$node_standby->safe_psql(
+	'postgres', qq[
+	ALTER SYSTEM SET primary_conninfo = '';
+	SELECT pg_reload_conf();
+]);
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 3; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start WRITE waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' MODE WRITE WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'write_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 3 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql(
+	'postgres', qq[
+	ALTER SYSTEM SET primary_conninfo = '$orig_conninfo';
+	SELECT pg_reload_conf();
+]);
+$node_standby->poll_query_until('postgres',
+	"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 3; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '$write_lsns[2]'::pg_lsn);");
+
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+# Stop walreceiver to ensure waiters actually block.
+$node_standby->safe_psql(
+	'postgres', qq[
+	ALTER SYSTEM SET primary_conninfo = '';
+	SELECT pg_reload_conf();
+]);
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 3; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start FLUSH waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' MODE FLUSH WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'flush_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 3 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql(
+	'postgres', qq[
+	ALTER SYSTEM SET primary_conninfo = '$orig_conninfo';
+	SELECT pg_reload_conf();
+]);
+$node_standby->poll_query_until('postgres',
+	"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 3; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[2]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  We stop the walreceiver and pause replay to
+# ensure all waiters block.  Then we resume replay and restart the
+# walreceiver to verify they unblock and complete correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+my $orig_conninfo_7d = $node_standby->safe_psql('postgres',
+	"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+$node_standby->safe_psql(
+	'postgres', qq[
+	ALTER SYSTEM SET primary_conninfo = '';
+	SELECT pg_reload_conf();
+]);
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('REPLAY', 'WRITE', 'FLUSH');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' MODE $mixed_modes[$i % 3] WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'mixed_done %', $i; END \$\$;
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+$node_standby->safe_psql(
+	'postgres', qq[
+	ALTER SYSTEM SET primary_conninfo = '$orig_conninfo_7d';
+	SELECT pg_reload_conf();
+]);
+$node_standby->poll_query_until('postgres',
+	"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('REPLAY', 'WRITE', 'FLUSH');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' MODE $wait_modes[$i];
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,17 +520,24 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
 
-$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+ok(1, 'promotion interrupted all wait modes');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}' MODE REPLAY;");
 
 ok(1, 'wait for already replayed LSN exits immediately even after promotion');
 
 $output = $node_standby->safe_psql(
 	'postgres', qq[
-	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+	WAIT FOR LSN '${lsn4}' MODE REPLAY WITH (timeout '10ms', no_throw);]);
 ok($output eq "not in recovery",
 	"WAIT FOR returns correct status after standby promotion");
 
@@ -295,8 +545,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-02 03:08                                                                             ` Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-02 03:08 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Mon, Dec 1, 2025 at 12:33 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi hackers,
>
> On Tue, Nov 25, 2025 at 7:51 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi!
> >
> > > > > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > > > > If we intend to extend its functionality more broadly, one option is
> > > > > > to add a mode option or something similar. Are users expected to wait
> > > > > > for flush(or others) completion in such cases? If not, and the TAP
> > > > > > test is the only intended use, this approach might be a bit of an
> > > > > > overkill.
> > > > >
> > > > > I would say that adding mode parameter seems to be a pretty natural
> > > > > extension of what we have at the moment.  I can imagine some
> > > > > clustering solution can use it to wait for certain transaction to be
> > > > > flushed at the replica (without delaying the commit at the primary).
> > > > >
> > > > > ------
> > > > > Regards,
> > > > > Alexander Korotkov
> > > > > Supabase
> > > >
> > > > Makes sense. I'll play with it and try to prepare a follow-up patch.
> > > >
> > > > --
> > > > Best,
> > > > Xuneng
> > >
> > > In terms of extending the functionality of the command, I see two
> > > possible approaches here. One is to keep mode as a mandatory keyword,
> > > and the other is to introduce it as an option in the WITH clause.
> > >
> > > Syntax Option A: Mode in the WITH Clause
> > >
> > > WAIT FOR LSN '0/12345' WITH (mode = 'replay');
> > > WAIT FOR LSN '0/12345' WITH (mode = 'flush');
> > > WAIT FOR LSN '0/12345' WITH (mode = 'write');
> > >
> > > With this option, we can keep "replay" as the default mode. That means
> > > existing TAP tests won’t need to be refactored unless they explicitly
> > > want a different mode.
> > >
> > > Syntax Option B: Mode as Part of the Main Command
> > >
> > > WAIT FOR LSN '0/12345' MODE 'replay';
> > > WAIT FOR LSN '0/12345' MODE 'flush';
> > > WAIT FOR LSN '0/12345' MODE 'write';
> > >
> > > Or a more concise variant using keywords:
> > >
> > > WAIT FOR LSN '0/12345' REPLAY;
> > > WAIT FOR LSN '0/12345' FLUSH;
> > > WAIT FOR LSN '0/12345' WRITE;
> > >
> > > This option produces a cleaner syntax if the intent is simply to wait
> > > for a particular LSN type, without specifying additional options like
> > > timeout or no_throw.
> > >
> > > I don’t have a clear preference among them. I’d be interested to hear
> > > what you or others think is the better direction.
> > >
> >
> > I've implemented a patch that adds MODE support to WAIT FOR LSN
> >
> > The new grammar looks like:
> >
> > ——
> > WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]
> > ——
> >
> > Two modes added: flush and write
> >
> > Design decisions:
> >
> > 1. MODE as a separate keyword (not in WITH clause) - This follows the
> > pattern used by LOCK command. It also makes the common case more
> > concise.
> >
> > 2. REPLAY as the default - When MODE is not specified, it defaults to REPLAY.
> >
> > 3. Keywords rather than strings - Using `MODE WRITE` rather than `MODE 'write'`
> >
> > The patch set includes:
> > -------
> > 0001 - Extend xlogwait infrastructure with write and flush wait types
> >
> > Adds WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH to WaitLSNType enum,
> > along with corresponding wait events and pairing heaps. Introduces
> > GetCurrentLSNForWaitType() to retrieve the appropriate LSN based on
> > wait type, and adds wakeup calls in walreceiver for write/flush
> > events.
> >
> > -------
> > 0002 - Add pg_last_wal_write_lsn() SQL function
> >
> > Adds a new SQL function that returns the current WAL write position on
> > a standby using GetWalRcvWriteRecPtr(). This complements existing
> > pg_last_wal_receive_lsn() (flush) and pg_last_wal_replay_lsn()
> > functions, enabling verification of WAIT FOR LSN MODE WRITE in TAP
> > tests.
> >
> > -------
> > 0003 - Add MODE parameter to WAIT FOR LSN command
> >
> > Extends the parser and executor to support the optional MODE
> > parameter. Updates documentation with new syntax and mode
> > descriptions. Adds TAP tests covering all three modes including
> > mixed-mode concurrent waiters.
> >
> > -------
> > 0004 - Add tab completion for WAIT FOR LSN MODE parameter
> >
> > Adds psql tab completion support: completes MODE after LSN value,
> > completes REPLAY/WRITE/FLUSH after MODE keyword, and completes WITH
> > after mode selection.
> >
> > -------
> > 0005 - Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()
> >
> > Replaces polling-based wait_for_catchup() with WAIT FOR LSN when the
> > target is a standby in recovery, improving test efficiency by avoiding
> > repeated queries.
> >
> > The WRITE and FLUSH modes enable scenarios where applications need to
> > ensure WAL has been received or persisted on the standby without
> > waiting for replay to complete.
> >
> > Feedback welcome.
> >
>
> Here is the updated v2 patch set. Most of the updates are in patch 3.
>
> Changes from v1:
>
> Patch 1 (Extend wait types in xlogwait infra)
> - Renamed enum values for consistency (WAIT_LSN_TYPE_REPLAY →
> WAIT_LSN_TYPE_REPLAY_STANDBY, etc.)
>
> Patch 2 (pg_last_wal_write_lsn):
> - Clarified documentation and comment
> - Improved pg_proc.dat description
>
> Patch 3 (MODE parameter):
> - Replaced direct cast with explicit switch statement for WaitLSNMode
> → WaitLSNType conversion
> - Improved FLUSH/WRITE mode documentation with verification function references
> - TAP tests (7b, 7c, 7d): Added walreceiver control for concurrency,
> explicit blocking verification via poll_query_until, and log-based
> completion verification via wait_for_log
> - Fix the timing issue in wait for all three sessions to get the
> errors after promotion of tap test 8.
>
> --
> Best,
> Xuneng

Here is the updated v3. The changes are made to patch 3:

- Refactor duplicated TAP test code by extracting helper routines for
starting and stopping walreceiver.
- Increase the number of concurrent WRITE and FLUSH waiters in tests
7b and 7c from three to five, matching the number in test 7a.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v3-0005-Use-WAIT-FOR-LSN-in.patch (3.1K, ../../CABPTF7VZgB0O1eYd-6BmYGw2a6qQN1XTts0a2VshSG5xMRObPQ@mail.gmail.com/2-v3-0005-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From 48f072498a128eb47f616e8c7e2621eb1ff2d831 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:20:18 +0800
Subject: [PATCH v3 5/5] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 35 ++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 35413f14019..b2a4e2e2253 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3328,6 +3328,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3340,9 +3343,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3360,6 +3360,35 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		# Map mode names to WAIT FOR LSN MODE values (uppercase)
+		my $wait_mode = uc($mode);
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' MODE ${wait_mode} WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v3-0002-Add-pg_last_wal_write_lsn-SQL-function.patch (3.7K, ../../CABPTF7VZgB0O1eYd-6BmYGw2a6qQN1XTts0a2VshSG5xMRObPQ@mail.gmail.com/3-v3-0002-Add-pg_last_wal_write_lsn-SQL-function.patch)
  download | inline diff:
From 9d22e09d378e8f6c52aa95bc4a0e1650f4621a39 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:07:52 +0800
Subject: [PATCH v3 2/5] Add pg_last_wal_write_lsn() SQL function

Returns the current WAL write position on a standby server using
GetWalRcvWriteRecPtr(). This enables verification of WAIT FOR LSN MODE WRITE
and operational monitoring of standby WAL write progress.
---
 doc/src/sgml/func/func-admin.sgml      | 22 ++++++++++++++++++++++
 src/backend/access/transam/xlogfuncs.c | 20 ++++++++++++++++++++
 src/include/catalog/pg_proc.dat        |  4 ++++
 3 files changed, 46 insertions(+)

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 1b465bc8ba7..9ff196c4be4 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -688,6 +688,28 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_last_wal_write_lsn</primary>
+        </indexterm>
+        <function>pg_last_wal_write_lsn</function> ()
+        <returnvalue>pg_lsn</returnvalue>
+       </para>
+       <para>
+        Returns the last write-ahead log location that has been received and
+        passed to the operating system by streaming replication, but not
+        necessarily synced to durable storage.  This is faster than
+        <function>pg_last_wal_receive_lsn</function> but provides weaker
+        durability guarantees since the data may still be in OS buffers.
+        While streaming replication is in progress this will increase
+        monotonically. If recovery has completed then this will remain static
+        at the location of the last WAL record written during recovery. If
+        streaming replication is disabled, or if it has not yet started, the
+        function returns <literal>NULL</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 3e45fce43ed..2797b2bf158 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -347,6 +347,26 @@ pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
 	PG_RETURN_LSN(recptr);
 }
 
+/*
+ * Report the last WAL write location (same format as pg_backup_start etc)
+ *
+ * This is useful for determining how much of WAL has been received and
+ * passed to the operating system by walreceiver.  Unlike pg_last_wal_receive_lsn,
+ * this data may still be in OS buffers and not yet synced to durable storage.
+ */
+Datum
+pg_last_wal_write_lsn(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr	recptr;
+
+	recptr = GetWalRcvWriteRecPtr();
+
+	if (!XLogRecPtrIsValid(recptr))
+		PG_RETURN_NULL();
+
+	PG_RETURN_LSN(recptr);
+}
+
 /*
  * Report the last WAL replay location (same format as pg_backup_start etc)
  *
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66431940700..478e0a8139f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6782,6 +6782,10 @@
   proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
   prorettype => 'pg_lsn', proargtypes => '',
   prosrc => 'pg_last_wal_receive_lsn' },
+{ oid => '6434', descr => 'last wal write location on standby',
+  proname => 'pg_last_wal_write_lsn', provolatile => 'v',
+  prorettype => 'pg_lsn', proargtypes => '',
+  prosrc => 'pg_last_wal_write_lsn' },
 { oid => '3821', descr => 'last wal replay location',
   proname => 'pg_last_wal_replay_lsn', provolatile => 'v',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.51.0



  [application/octet-stream] v3-0003-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch (38.8K, ../../CABPTF7VZgB0O1eYd-6BmYGw2a6qQN1XTts0a2VshSG5xMRObPQ@mail.gmail.com/4-v3-0003-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From d51394bdfdf16e0d569a0e5843288c1a36b671a5 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:11:54 +0800
Subject: [PATCH v3 3/5] Add MODE parameter to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE parameter that
specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]

- REPLAY (default): Wait for WAL to be replayed to the specified LSN
- WRITE: Wait for WAL to be written (received) to the specified LSN
- FLUSH: Wait for WAL to be flushed to disk at the specified LSN

The default mode is REPLAY, matching the original behavior when MODE
is not specified. This follows the pattern used by LOCK command where
the mode parameter is optional with a sensible default.

The WRITE and FLUSH modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and refactoring
  of existing WAIT FOR command documentation
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for WRITE/FLUSH waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 188 +++++++++++----
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  64 +++++-
 src/backend/parser/gram.y               |  21 +-
 src/backend/replication/walreceiver.c   |  19 ++
 src/include/nodes/parsenodes.h          |  11 +
 src/include/parser/kwlist.h             |   2 +
 src/test/recovery/t/049_wait_for_lsn.pl | 294 ++++++++++++++++++++++--
 8 files changed, 518 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..a5e7f6c6fe9 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,12 +16,13 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ MODE { REPLAY | FLUSH | WRITE } ]
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
@@ -34,20 +35,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -64,6 +67,57 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      Specifies the type of LSN processing to wait for. If not specified,
+      the default is <literal>REPLAY</literal>. The valid modes are:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term><literal>REPLAY</literal></term>
+       <listitem>
+        <para>
+         Wait for the LSN to be replayed (applied to the database).
+         After successful completion, <function>pg_last_wal_replay_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FLUSH</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and synced to durable storage via <function>fsync()</function>.
+         This provides a durability guarantee without waiting for the WAL
+         to be applied. After successful completion,
+         <function>pg_last_wal_receive_lsn()</function> will return a value
+         greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>WRITE</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and passed to the operating system via <function>write()</function>.
+         This is faster than <literal>FLUSH</literal> but provides weaker
+         durability guarantees since the data may still be in OS buffers.
+         After successful completion, <function>pg_last_wal_write_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
     <listitem>
@@ -135,9 +189,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +205,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +240,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -198,9 +263,9 @@ pg_current_wal_insert_lsn
    changes made on primary should be guaranteed to be visible on replica.
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20';
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY;
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,21 +276,46 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+postgres=# WAIT FOR LSN '0/306EE20' MODE FLUSH;
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE WRITE WITH (TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '0.1s');
 ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
 </programlisting>
   </para>
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4b145515269..5b2a262ff8e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6240,10 +6240,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_WRITE_STANDBY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 43b37095afb..05ad84fdb5b 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,12 +29,28 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_REPLAY_STANDBY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_WRITE_STANDBY] = {"write", "written"},
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = {"flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType;
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
@@ -45,6 +62,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
 
+	/* Convert parse-time WaitLSNMode to runtime WaitLSNType */
+	switch (stmt->mode)
+	{
+		case WAIT_LSN_MODE_REPLAY:
+			lsnType = WAIT_LSN_TYPE_REPLAY_STANDBY;
+			break;
+		case WAIT_LSN_MODE_WRITE:
+			lsnType = WAIT_LSN_TYPE_WRITE_STANDBY;
+			break;
+		case WAIT_LSN_MODE_FLUSH:
+			lsnType = WAIT_LSN_TYPE_FLUSH_STANDBY;
+			break;
+		default:
+			elog(ERROR, "unrecognized wait mode: %d", stmt->mode);
+	}
+
 	foreach_node(DefElem, defel, stmt->options)
 	{
 		if (strcmp(defel->defname, "timeout") == 0)
@@ -107,8 +140,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +173,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY_STANDBY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +187,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +206,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..57b3e91893c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -640,6 +640,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
+%type <ival>	opt_wait_lsn_mode
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <boolean> opt_unique_null_treatment
@@ -729,7 +730,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
 	EXPRESSION EXTENSION EXTERNAL EXTRACT
 
-	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
+	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FLUSH FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -770,7 +771,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLAY REPLICA
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -16489,15 +16490,23 @@ xml_passing_mech:
  *****************************************************************************/
 
 WaitStmt:
-			WAIT FOR LSN_P Sconst opt_wait_with_clause
+			WAIT FOR LSN_P Sconst opt_wait_lsn_mode opt_wait_with_clause
 				{
 					WaitStmt *n = makeNode(WaitStmt);
 					n->lsn_literal = $4;
-					n->options = $5;
+					n->mode = $5;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
 			;
 
+opt_wait_lsn_mode:
+			MODE REPLAY			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			| MODE FLUSH		{ $$ = WAIT_LSN_MODE_FLUSH; }
+			| MODE WRITE		{ $$ = WAIT_LSN_MODE_WRITE; }
+			| /*EMPTY*/			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			;
+
 opt_wait_with_clause:
 			WITH '(' utility_option_list ')'		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -17937,6 +17946,7 @@ unreserved_keyword:
 			| FILTER
 			| FINALIZE
 			| FIRST_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FORMAT
@@ -18071,6 +18081,7 @@ unreserved_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESPECT_P
@@ -18524,6 +18535,7 @@ bare_label_keyword:
 			| FINALIZE
 			| FIRST_P
 			| FLOAT_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FOREIGN
@@ -18706,6 +18718,7 @@ bare_label_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESTART
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 4217fc54e2e..be2971408e7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_WRITE_STANDBY])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_WRITE_STANDBY, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_FLUSH_STANDBY])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d14294a4ece..bbaf3242ccb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,10 +4385,21 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+/*
+ * WaitLSNMode - MODE parameter for WAIT FOR command
+ */
+typedef enum WaitLSNMode
+{
+	WAIT_LSN_MODE_REPLAY,		/* Wait for LSN replay on standby */
+	WAIT_LSN_MODE_WRITE,		/* Wait for LSN write on standby */
+	WAIT_LSN_MODE_FLUSH			/* Wait for LSN flush on standby */
+}			WaitLSNMode;
+
 typedef struct WaitStmt
 {
 	NodeTag		type;
 	char	   *lsn_literal;	/* LSN string from grammar */
+	WaitLSNMode mode;			/* Wait mode: REPLAY/FLUSH/WRITE */
 	List	   *options;		/* List of DefElem nodes */
 } WaitStmt;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..7ad8b11b725 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -176,6 +176,7 @@ PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("flush", FLUSH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -378,6 +379,7 @@ PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("replay", REPLAY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD, AS_LABEL)
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..ee3f2bf30d6 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -7,6 +7,38 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql('postgres',
+		"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '$saved_primary_conninfo';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +94,34 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' MODE WRITE WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '${lsn_write}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR MODE WRITE");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' MODE FLUSH WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR MODE FLUSH");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +147,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +184,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +267,7 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -226,7 +285,9 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
 \$\$
 LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -239,10 +300,11 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->query_until(
 		qr/start/, qq[
 		\\echo start
-		WAIT FOR LSN '${lsn}';
+		WAIT FOR LSN '${lsn}' MODE REPLAY;
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +313,199 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
+
+# 7b. Check the scenario of multiple WRITE waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start WRITE waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' MODE WRITE WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'write_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '$write_lsns[4]'::pg_lsn);");
+
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start FLUSH waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' MODE FLUSH WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'flush_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  We stop the walreceiver and pause replay to
+# ensure all waiters block.  Then we resume replay and restart the
+# walreceiver to verify they unblock and complete correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('REPLAY', 'WRITE', 'FLUSH');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' MODE $mixed_modes[$i % 3] WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'mixed_done %', $i; END \$\$;
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
+
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp(pg_last_wal_write_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('REPLAY', 'WRITE', 'FLUSH');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' MODE $wait_modes[$i];
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,17 +515,24 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
 
-$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+ok(1, 'promotion interrupted all wait modes');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}' MODE REPLAY;");
 
 ok(1, 'wait for already replayed LSN exits immediately even after promotion');
 
 $output = $node_standby->safe_psql(
 	'postgres', qq[
-	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+	WAIT FOR LSN '${lsn4}' MODE REPLAY WITH (timeout '10ms', no_throw);]);
 ok($output eq "not in recovery",
 	"WAIT FOR returns correct status after standby promotion");
 
@@ -295,8 +540,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



  [application/octet-stream] v3-0004-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch (3.2K, ../../CABPTF7VZgB0O1eYd-6BmYGw2a6qQN1XTts0a2VshSG5xMRObPQ@mail.gmail.com/5-v3-0004-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch)
  download | inline diff:
From 51624191461fe702522c315d9da7a68da48a4b13 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:18:06 +0800
Subject: [PATCH v3 4/5] Add tab completion for WAIT FOR LSN MODE parameter

Update psql tab completion to support the optional MODE parameter in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords since MODE defaults to REPLAY.
---
 src/bin/psql/tab-complete.in.c | 39 ++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 20d7a65c614..fcb9f19faef 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5313,10 +5313,11 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /*
- * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
+ * WAIT FOR LSN '<lsn>' [ MODE { REPLAY | FLUSH | WRITE } ] [ WITH ( option [, ...] ) ]
  * where option can be:
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * MODE defaults to REPLAY if not specified.
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5325,25 +5326,41 @@ match_previous_words(int pattern_id,
 	else if (Matches("WAIT", "FOR", "LSN"))
 		/* No completion for LSN value - user must provide manually */
 		;
+
+	/*
+	 * After LSN value, offer MODE (optional) or WITH, since MODE defaults to
+	 * REPLAY
+	 */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny))
+		COMPLETE_WITH("MODE", "WITH");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE"))
+		COMPLETE_WITH("REPLAY", "FLUSH", "WRITE");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny))
 		COMPLETE_WITH("WITH");
+	/* WITH directly after LSN (using default REPLAY mode) */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list (both with and without explicit MODE).
+	 * This fires when we're in an unfinished parenthesized option list.
+	 * get_previous_words treats a completed parenthesized option list as one
+	 * word, so the above test is correct. timeout takes a string value,
+	 * no_throw takes no value. We don't offer completions for these values.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
 			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+	}
+	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*") &&
+			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*)"))
+	{
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("timeout", "no_throw");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v3-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (10.5K, ../../CABPTF7VZgB0O1eYd-6BmYGw2a6qQN1XTts0a2VshSG5xMRObPQ@mail.gmail.com/6-v3-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From da210bfc2b62d9a38ea54b94037380144753663a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 17:58:28 +0800
Subject: [PATCH v3 1/5] Extend xlogwait infrastructure with write and flush 
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_WRITE_STANDBY and WAIT_LSN_TYPE_FLUSH_STANDBY to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 84 ++++++++++++++-----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 13 ++-
 6 files changed, 81 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 22d0a2e8c3a..4b145515269 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6243,7 +6243,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 21b8f179ba0..243c0b368a9 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1846,8 +1846,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY_STANDBY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 98aa5f1e4a2..21823acee9c 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,48 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_REPLAY_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_WRITE_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_FLUSH_PRIMARY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_REPLAY_STANDBY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_WRITE_STANDBY:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_FLUSH_STANDBY:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_FLUSH_PRIMARY:
+			return GetFlushRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_COUNT:
+			break;
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +389,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_FLUSH_PRIMARY && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +422,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..43b37095afb 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY_STANDBY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c1ac71ff7f2..fbcdb92dcfb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index e607441d618..9721a7a7195 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -35,9 +35,15 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
-	WAIT_LSN_TYPE_COUNT = 2
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_REPLAY_STANDBY = 0,
+	WAIT_LSN_TYPE_WRITE_STANDBY = 1,
+	WAIT_LSN_TYPE_FLUSH_STANDBY = 2,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_FLUSH_PRIMARY = 3,
+
+	WAIT_LSN_TYPE_COUNT = 4
 } WaitLSNType;
 
 /*
@@ -96,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-02 10:10                                                                               ` Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-02 10:10 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Dec 2, 2025 at 11:08 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Mon, Dec 1, 2025 at 12:33 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi hackers,
> >
> > On Tue, Nov 25, 2025 at 7:51 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi!
> > >
> > > > > > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > > > > > If we intend to extend its functionality more broadly, one option is
> > > > > > > to add a mode option or something similar. Are users expected to wait
> > > > > > > for flush(or others) completion in such cases? If not, and the TAP
> > > > > > > test is the only intended use, this approach might be a bit of an
> > > > > > > overkill.
> > > > > >
> > > > > > I would say that adding mode parameter seems to be a pretty natural
> > > > > > extension of what we have at the moment.  I can imagine some
> > > > > > clustering solution can use it to wait for certain transaction to be
> > > > > > flushed at the replica (without delaying the commit at the primary).
> > > > > >
> > > > > > ------
> > > > > > Regards,
> > > > > > Alexander Korotkov
> > > > > > Supabase
> > > > >
> > > > > Makes sense. I'll play with it and try to prepare a follow-up patch.
> > > > >
> > > > > --
> > > > > Best,
> > > > > Xuneng
> > > >
> > > > In terms of extending the functionality of the command, I see two
> > > > possible approaches here. One is to keep mode as a mandatory keyword,
> > > > and the other is to introduce it as an option in the WITH clause.
> > > >
> > > > Syntax Option A: Mode in the WITH Clause
> > > >
> > > > WAIT FOR LSN '0/12345' WITH (mode = 'replay');
> > > > WAIT FOR LSN '0/12345' WITH (mode = 'flush');
> > > > WAIT FOR LSN '0/12345' WITH (mode = 'write');
> > > >
> > > > With this option, we can keep "replay" as the default mode. That means
> > > > existing TAP tests won’t need to be refactored unless they explicitly
> > > > want a different mode.
> > > >
> > > > Syntax Option B: Mode as Part of the Main Command
> > > >
> > > > WAIT FOR LSN '0/12345' MODE 'replay';
> > > > WAIT FOR LSN '0/12345' MODE 'flush';
> > > > WAIT FOR LSN '0/12345' MODE 'write';
> > > >
> > > > Or a more concise variant using keywords:
> > > >
> > > > WAIT FOR LSN '0/12345' REPLAY;
> > > > WAIT FOR LSN '0/12345' FLUSH;
> > > > WAIT FOR LSN '0/12345' WRITE;
> > > >
> > > > This option produces a cleaner syntax if the intent is simply to wait
> > > > for a particular LSN type, without specifying additional options like
> > > > timeout or no_throw.
> > > >
> > > > I don’t have a clear preference among them. I’d be interested to hear
> > > > what you or others think is the better direction.
> > > >
> > >
> > > I've implemented a patch that adds MODE support to WAIT FOR LSN
> > >
> > > The new grammar looks like:
> > >
> > > ——
> > > WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]
> > > ——
> > >
> > > Two modes added: flush and write
> > >
> > > Design decisions:
> > >
> > > 1. MODE as a separate keyword (not in WITH clause) - This follows the
> > > pattern used by LOCK command. It also makes the common case more
> > > concise.
> > >
> > > 2. REPLAY as the default - When MODE is not specified, it defaults to REPLAY.
> > >
> > > 3. Keywords rather than strings - Using `MODE WRITE` rather than `MODE 'write'`
> > >
> > > The patch set includes:
> > > -------
> > > 0001 - Extend xlogwait infrastructure with write and flush wait types
> > >
> > > Adds WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH to WaitLSNType enum,
> > > along with corresponding wait events and pairing heaps. Introduces
> > > GetCurrentLSNForWaitType() to retrieve the appropriate LSN based on
> > > wait type, and adds wakeup calls in walreceiver for write/flush
> > > events.
> > >
> > > -------
> > > 0002 - Add pg_last_wal_write_lsn() SQL function
> > >
> > > Adds a new SQL function that returns the current WAL write position on
> > > a standby using GetWalRcvWriteRecPtr(). This complements existing
> > > pg_last_wal_receive_lsn() (flush) and pg_last_wal_replay_lsn()
> > > functions, enabling verification of WAIT FOR LSN MODE WRITE in TAP
> > > tests.
> > >
> > > -------
> > > 0003 - Add MODE parameter to WAIT FOR LSN command
> > >
> > > Extends the parser and executor to support the optional MODE
> > > parameter. Updates documentation with new syntax and mode
> > > descriptions. Adds TAP tests covering all three modes including
> > > mixed-mode concurrent waiters.
> > >
> > > -------
> > > 0004 - Add tab completion for WAIT FOR LSN MODE parameter
> > >
> > > Adds psql tab completion support: completes MODE after LSN value,
> > > completes REPLAY/WRITE/FLUSH after MODE keyword, and completes WITH
> > > after mode selection.
> > >
> > > -------
> > > 0005 - Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()
> > >
> > > Replaces polling-based wait_for_catchup() with WAIT FOR LSN when the
> > > target is a standby in recovery, improving test efficiency by avoiding
> > > repeated queries.
> > >
> > > The WRITE and FLUSH modes enable scenarios where applications need to
> > > ensure WAL has been received or persisted on the standby without
> > > waiting for replay to complete.
> > >
> > > Feedback welcome.
> > >
> >
> > Here is the updated v2 patch set. Most of the updates are in patch 3.
> >
> > Changes from v1:
> >
> > Patch 1 (Extend wait types in xlogwait infra)
> > - Renamed enum values for consistency (WAIT_LSN_TYPE_REPLAY →
> > WAIT_LSN_TYPE_REPLAY_STANDBY, etc.)
> >
> > Patch 2 (pg_last_wal_write_lsn):
> > - Clarified documentation and comment
> > - Improved pg_proc.dat description
> >
> > Patch 3 (MODE parameter):
> > - Replaced direct cast with explicit switch statement for WaitLSNMode
> > → WaitLSNType conversion
> > - Improved FLUSH/WRITE mode documentation with verification function references
> > - TAP tests (7b, 7c, 7d): Added walreceiver control for concurrency,
> > explicit blocking verification via poll_query_until, and log-based
> > completion verification via wait_for_log
> > - Fix the timing issue in wait for all three sessions to get the
> > errors after promotion of tap test 8.
> >
> > --
> > Best,
> > Xuneng
>
> Here is the updated v3. The changes are made to patch 3:
>
> - Refactor duplicated TAP test code by extracting helper routines for
> starting and stopping walreceiver.
> - Increase the number of concurrent WRITE and FLUSH waiters in tests
> 7b and 7c from three to five, matching the number in test 7a.
>
> --
> Best,
> Xuneng

Just realized that patch 2 in prior emails could be dropped for
simplicity. Since the write LSN can be retrieved directly from
pg_stat_wal_receiver, the TAP test in patch 3 does not require a
separate SQL function for this purpose alone.


-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v4-0004-Use-WAIT-FOR-LSN-in.patch (3.1K, ../../CABPTF7U2cYN=bMZirqj93Zv-aqBdw4f=wPRwovTzWKP=adYhDg@mail.gmail.com/2-v4-0004-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From 56044afa03fe5732460c8de28039915133137602 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:20:18 +0800
Subject: [PATCH v4 4/4] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 35 ++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 35413f14019..b2a4e2e2253 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3328,6 +3328,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3340,9 +3343,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3360,6 +3360,35 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		# Map mode names to WAIT FOR LSN MODE values (uppercase)
+		my $wait_mode = uc($mode);
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' MODE ${wait_mode} WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v4-0002-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch (39.2K, ../../CABPTF7U2cYN=bMZirqj93Zv-aqBdw4f=wPRwovTzWKP=adYhDg@mail.gmail.com/3-v4-0002-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From c0748a75838fe9281a15f56976f3059596943fd3 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:11:54 +0800
Subject: [PATCH v4 2/4] Add MODE parameter to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE parameter that
specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]

- REPLAY (default): Wait for WAL to be replayed to the specified LSN
- WRITE: Wait for WAL to be written (received) to the specified LSN
- FLUSH: Wait for WAL to be flushed to disk at the specified LSN

The default mode is REPLAY, matching the original behavior when MODE
is not specified. This follows the pattern used by LOCK command where
the mode parameter is optional with a sensible default.

The WRITE and FLUSH modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and refactoring
  of existing WAIT FOR command documentation
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for WRITE/FLUSH waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 192 ++++++++++++----
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  64 +++++-
 src/backend/parser/gram.y               |  21 +-
 src/backend/replication/walreceiver.c   |  19 ++
 src/include/nodes/parsenodes.h          |  11 +
 src/include/parser/kwlist.h             |   2 +
 src/test/recovery/t/049_wait_for_lsn.pl | 294 ++++++++++++++++++++++--
 8 files changed, 522 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..28c68678315 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,12 +16,13 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ MODE { REPLAY | FLUSH | WRITE } ]
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
@@ -34,20 +35,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -64,6 +67,61 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      Specifies the type of LSN processing to wait for. If not specified,
+      the default is <literal>REPLAY</literal>. The valid modes are:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term><literal>REPLAY</literal></term>
+       <listitem>
+        <para>
+         Wait for the LSN to be replayed (applied to the database).
+         After successful completion, <function>pg_last_wal_replay_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FLUSH</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and flushed to disk. This provides a durability guarantee
+         without waiting for the WAL to be applied. After successful
+         completion, <function>pg_last_wal_receive_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+         This value is also available as the <structfield>flushed_lsn</structfield>
+         column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+         <structname>pg_stat_wal_receiver</structname></link>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>WRITE</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and written to disk, but not yet flushed. This is faster
+         than <literal>FLUSH</literal> but provides weaker durability
+         guarantees since the data may still be in operating system buffers.
+         After successful completion, the <structfield>written_lsn</structfield>
+         column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+         <structname>pg_stat_wal_receiver</structname></link> will show
+         a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
     <listitem>
@@ -135,9 +193,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +209,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +244,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -198,9 +267,9 @@ pg_current_wal_insert_lsn
    changes made on primary should be guaranteed to be visible on replica.
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20';
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY;
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,21 +280,46 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+postgres=# WAIT FOR LSN '0/306EE20' MODE FLUSH;
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE WRITE WITH (TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '0.1s');
 ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
 </programlisting>
   </para>
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4b145515269..5b2a262ff8e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6240,10 +6240,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_WRITE_STANDBY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 43b37095afb..05ad84fdb5b 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,12 +29,28 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_REPLAY_STANDBY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_WRITE_STANDBY] = {"write", "written"},
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = {"flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType;
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
@@ -45,6 +62,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
 
+	/* Convert parse-time WaitLSNMode to runtime WaitLSNType */
+	switch (stmt->mode)
+	{
+		case WAIT_LSN_MODE_REPLAY:
+			lsnType = WAIT_LSN_TYPE_REPLAY_STANDBY;
+			break;
+		case WAIT_LSN_MODE_WRITE:
+			lsnType = WAIT_LSN_TYPE_WRITE_STANDBY;
+			break;
+		case WAIT_LSN_MODE_FLUSH:
+			lsnType = WAIT_LSN_TYPE_FLUSH_STANDBY;
+			break;
+		default:
+			elog(ERROR, "unrecognized wait mode: %d", stmt->mode);
+	}
+
 	foreach_node(DefElem, defel, stmt->options)
 	{
 		if (strcmp(defel->defname, "timeout") == 0)
@@ -107,8 +140,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +173,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY_STANDBY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +187,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +206,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..57b3e91893c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -640,6 +640,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
+%type <ival>	opt_wait_lsn_mode
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <boolean> opt_unique_null_treatment
@@ -729,7 +730,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
 	EXPRESSION EXTENSION EXTERNAL EXTRACT
 
-	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
+	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FLUSH FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -770,7 +771,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLAY REPLICA
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -16489,15 +16490,23 @@ xml_passing_mech:
  *****************************************************************************/
 
 WaitStmt:
-			WAIT FOR LSN_P Sconst opt_wait_with_clause
+			WAIT FOR LSN_P Sconst opt_wait_lsn_mode opt_wait_with_clause
 				{
 					WaitStmt *n = makeNode(WaitStmt);
 					n->lsn_literal = $4;
-					n->options = $5;
+					n->mode = $5;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
 			;
 
+opt_wait_lsn_mode:
+			MODE REPLAY			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			| MODE FLUSH		{ $$ = WAIT_LSN_MODE_FLUSH; }
+			| MODE WRITE		{ $$ = WAIT_LSN_MODE_WRITE; }
+			| /*EMPTY*/			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			;
+
 opt_wait_with_clause:
 			WITH '(' utility_option_list ')'		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -17937,6 +17946,7 @@ unreserved_keyword:
 			| FILTER
 			| FINALIZE
 			| FIRST_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FORMAT
@@ -18071,6 +18081,7 @@ unreserved_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESPECT_P
@@ -18524,6 +18535,7 @@ bare_label_keyword:
 			| FINALIZE
 			| FIRST_P
 			| FLOAT_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FOREIGN
@@ -18706,6 +18718,7 @@ bare_label_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESTART
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 4217fc54e2e..be2971408e7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_WRITE_STANDBY])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_WRITE_STANDBY, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_FLUSH_STANDBY])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_FLUSH_STANDBY, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d14294a4ece..bbaf3242ccb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4385,10 +4385,21 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+/*
+ * WaitLSNMode - MODE parameter for WAIT FOR command
+ */
+typedef enum WaitLSNMode
+{
+	WAIT_LSN_MODE_REPLAY,		/* Wait for LSN replay on standby */
+	WAIT_LSN_MODE_WRITE,		/* Wait for LSN write on standby */
+	WAIT_LSN_MODE_FLUSH			/* Wait for LSN flush on standby */
+}			WaitLSNMode;
+
 typedef struct WaitStmt
 {
 	NodeTag		type;
 	char	   *lsn_literal;	/* LSN string from grammar */
+	WaitLSNMode mode;			/* Wait mode: REPLAY/FLUSH/WRITE */
 	List	   *options;		/* List of DefElem nodes */
 } WaitStmt;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..7ad8b11b725 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -176,6 +176,7 @@ PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("flush", FLUSH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -378,6 +379,7 @@ PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("replay", REPLAY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD, AS_LABEL)
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..df7b563cfbb 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -7,6 +7,38 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql('postgres',
+		"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '$saved_primary_conninfo';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +94,34 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' MODE WRITE WITH (timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR MODE WRITE");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' MODE FLUSH WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR MODE FLUSH");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +147,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +184,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +267,7 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -226,7 +285,9 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
 \$\$
 LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -239,10 +300,11 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->query_until(
 		qr/start/, qq[
 		\\echo start
-		WAIT FOR LSN '${lsn}';
+		WAIT FOR LSN '${lsn}' MODE REPLAY;
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +313,199 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
+
+# 7b. Check the scenario of multiple WRITE waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start WRITE waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' MODE WRITE WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'write_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);");
+
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start FLUSH waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' MODE FLUSH WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'flush_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  We stop the walreceiver and pause replay to
+# ensure all waiters block.  Then we resume replay and restart the
+# walreceiver to verify they unblock and complete correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('REPLAY', 'WRITE', 'FLUSH');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' MODE $mixed_modes[$i % 3] WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'mixed_done %', $i; END \$\$;
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
+
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('REPLAY', 'WRITE', 'FLUSH');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' MODE $wait_modes[$i];
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,17 +515,24 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
 
-$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+ok(1, 'promotion interrupted all wait modes');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}' MODE REPLAY;");
 
 ok(1, 'wait for already replayed LSN exits immediately even after promotion');
 
 $output = $node_standby->safe_psql(
 	'postgres', qq[
-	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+	WAIT FOR LSN '${lsn4}' MODE REPLAY WITH (timeout '10ms', no_throw);]);
 ok($output eq "not in recovery",
 	"WAIT FOR returns correct status after standby promotion");
 
@@ -295,8 +540,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



  [application/octet-stream] v4-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch (3.2K, ../../CABPTF7U2cYN=bMZirqj93Zv-aqBdw4f=wPRwovTzWKP=adYhDg@mail.gmail.com/4-v4-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch)
  download | inline diff:
From af5b59d0e065ecb2f7b68c0eec8e55b892a5a435 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 19:18:06 +0800
Subject: [PATCH v4 3/4] Add tab completion for WAIT FOR LSN MODE parameter

Update psql tab completion to support the optional MODE parameter in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords since MODE defaults to REPLAY.
---
 src/bin/psql/tab-complete.in.c | 39 ++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 20d7a65c614..fcb9f19faef 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5313,10 +5313,11 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /*
- * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
+ * WAIT FOR LSN '<lsn>' [ MODE { REPLAY | FLUSH | WRITE } ] [ WITH ( option [, ...] ) ]
  * where option can be:
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * MODE defaults to REPLAY if not specified.
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5325,25 +5326,41 @@ match_previous_words(int pattern_id,
 	else if (Matches("WAIT", "FOR", "LSN"))
 		/* No completion for LSN value - user must provide manually */
 		;
+
+	/*
+	 * After LSN value, offer MODE (optional) or WITH, since MODE defaults to
+	 * REPLAY
+	 */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny))
+		COMPLETE_WITH("MODE", "WITH");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE"))
+		COMPLETE_WITH("REPLAY", "FLUSH", "WRITE");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny))
 		COMPLETE_WITH("WITH");
+	/* WITH directly after LSN (using default REPLAY mode) */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list (both with and without explicit MODE).
+	 * This fires when we're in an unfinished parenthesized option list.
+	 * get_previous_words treats a completed parenthesized option list as one
+	 * word, so the above test is correct. timeout takes a string value,
+	 * no_throw takes no value. We don't offer completions for these values.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
 			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+	}
+	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*") &&
+			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*)"))
+	{
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("timeout", "no_throw");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v4-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (10.5K, ../../CABPTF7U2cYN=bMZirqj93Zv-aqBdw4f=wPRwovTzWKP=adYhDg@mail.gmail.com/5-v4-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From da210bfc2b62d9a38ea54b94037380144753663a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 25 Nov 2025 17:58:28 +0800
Subject: [PATCH v4] Extend xlogwait infrastructure with write and flush  wait
 types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH_STANDBY to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 84 ++++++++++++++-----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 13 ++-
 6 files changed, 81 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 22d0a2e8c3a..4b145515269 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6243,7 +6243,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 21b8f179ba0..243c0b368a9 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1846,8 +1846,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY_STANDBY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY_STANDBY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 98aa5f1e4a2..21823acee9c 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,48 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_REPLAY_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_WRITE_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_FLUSH_STANDBY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_FLUSH_PRIMARY] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_REPLAY_STANDBY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_WRITE_STANDBY:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_FLUSH_STANDBY:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_FLUSH_PRIMARY:
+			return GetFlushRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_COUNT:
+			break;
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +389,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_FLUSH_PRIMARY && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +422,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..43b37095afb 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY_STANDBY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c1ac71ff7f2..fbcdb92dcfb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index e607441d618..9721a7a7195 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -35,9 +35,15 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY = 0,	/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH = 1,	/* Waiting for flush on primary */
-	WAIT_LSN_TYPE_COUNT = 2
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_REPLAY_STANDBY = 0,
+	WAIT_LSN_TYPE_WRITE_STANDBY = 1,
+	WAIT_LSN_TYPE_FLUSH_STANDBY = 2,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_FLUSH_PRIMARY = 3,
+
+	WAIT_LSN_TYPE_COUNT = 4
 } WaitLSNType;
 
 /*
@@ -96,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-16 03:28                                                                                 ` Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-16 03:28 UTC (permalink / raw)
  To: pgsql-hackers <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Dec 2, 2025 at 6:10 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Tue, Dec 2, 2025 at 11:08 AM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Mon, Dec 1, 2025 at 12:33 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi hackers,
> > >
> > > On Tue, Nov 25, 2025 at 7:51 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi!
> > > >
> > > > > > > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > > > > > > If we intend to extend its functionality more broadly, one option is
> > > > > > > > to add a mode option or something similar. Are users expected to wait
> > > > > > > > for flush(or others) completion in such cases? If not, and the TAP
> > > > > > > > test is the only intended use, this approach might be a bit of an
> > > > > > > > overkill.
> > > > > > >
> > > > > > > I would say that adding mode parameter seems to be a pretty natural
> > > > > > > extension of what we have at the moment.  I can imagine some
> > > > > > > clustering solution can use it to wait for certain transaction to be
> > > > > > > flushed at the replica (without delaying the commit at the primary).
> > > > > > >
> > > > > > > ------
> > > > > > > Regards,
> > > > > > > Alexander Korotkov
> > > > > > > Supabase
> > > > > >
> > > > > > Makes sense. I'll play with it and try to prepare a follow-up patch.
> > > > > >
> > > > > > --
> > > > > > Best,
> > > > > > Xuneng
> > > > >
> > > > > In terms of extending the functionality of the command, I see two
> > > > > possible approaches here. One is to keep mode as a mandatory keyword,
> > > > > and the other is to introduce it as an option in the WITH clause.
> > > > >
> > > > > Syntax Option A: Mode in the WITH Clause
> > > > >
> > > > > WAIT FOR LSN '0/12345' WITH (mode = 'replay');
> > > > > WAIT FOR LSN '0/12345' WITH (mode = 'flush');
> > > > > WAIT FOR LSN '0/12345' WITH (mode = 'write');
> > > > >
> > > > > With this option, we can keep "replay" as the default mode. That means
> > > > > existing TAP tests won’t need to be refactored unless they explicitly
> > > > > want a different mode.
> > > > >
> > > > > Syntax Option B: Mode as Part of the Main Command
> > > > >
> > > > > WAIT FOR LSN '0/12345' MODE 'replay';
> > > > > WAIT FOR LSN '0/12345' MODE 'flush';
> > > > > WAIT FOR LSN '0/12345' MODE 'write';
> > > > >
> > > > > Or a more concise variant using keywords:
> > > > >
> > > > > WAIT FOR LSN '0/12345' REPLAY;
> > > > > WAIT FOR LSN '0/12345' FLUSH;
> > > > > WAIT FOR LSN '0/12345' WRITE;
> > > > >
> > > > > This option produces a cleaner syntax if the intent is simply to wait
> > > > > for a particular LSN type, without specifying additional options like
> > > > > timeout or no_throw.
> > > > >
> > > > > I don’t have a clear preference among them. I’d be interested to hear
> > > > > what you or others think is the better direction.
> > > > >
> > > >
> > > > I've implemented a patch that adds MODE support to WAIT FOR LSN
> > > >
> > > > The new grammar looks like:
> > > >
> > > > ——
> > > > WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]
> > > > ——
> > > >
> > > > Two modes added: flush and write
> > > >
> > > > Design decisions:
> > > >
> > > > 1. MODE as a separate keyword (not in WITH clause) - This follows the
> > > > pattern used by LOCK command. It also makes the common case more
> > > > concise.
> > > >
> > > > 2. REPLAY as the default - When MODE is not specified, it defaults to REPLAY.
> > > >
> > > > 3. Keywords rather than strings - Using `MODE WRITE` rather than `MODE 'write'`
> > > >
> > > > The patch set includes:
> > > > -------
> > > > 0001 - Extend xlogwait infrastructure with write and flush wait types
> > > >
> > > > Adds WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH to WaitLSNType enum,
> > > > along with corresponding wait events and pairing heaps. Introduces
> > > > GetCurrentLSNForWaitType() to retrieve the appropriate LSN based on
> > > > wait type, and adds wakeup calls in walreceiver for write/flush
> > > > events.
> > > >
> > > > -------
> > > > 0002 - Add pg_last_wal_write_lsn() SQL function
> > > >
> > > > Adds a new SQL function that returns the current WAL write position on
> > > > a standby using GetWalRcvWriteRecPtr(). This complements existing
> > > > pg_last_wal_receive_lsn() (flush) and pg_last_wal_replay_lsn()
> > > > functions, enabling verification of WAIT FOR LSN MODE WRITE in TAP
> > > > tests.
> > > >
> > > > -------
> > > > 0003 - Add MODE parameter to WAIT FOR LSN command
> > > >
> > > > Extends the parser and executor to support the optional MODE
> > > > parameter. Updates documentation with new syntax and mode
> > > > descriptions. Adds TAP tests covering all three modes including
> > > > mixed-mode concurrent waiters.
> > > >
> > > > -------
> > > > 0004 - Add tab completion for WAIT FOR LSN MODE parameter
> > > >
> > > > Adds psql tab completion support: completes MODE after LSN value,
> > > > completes REPLAY/WRITE/FLUSH after MODE keyword, and completes WITH
> > > > after mode selection.
> > > >
> > > > -------
> > > > 0005 - Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()
> > > >
> > > > Replaces polling-based wait_for_catchup() with WAIT FOR LSN when the
> > > > target is a standby in recovery, improving test efficiency by avoiding
> > > > repeated queries.
> > > >
> > > > The WRITE and FLUSH modes enable scenarios where applications need to
> > > > ensure WAL has been received or persisted on the standby without
> > > > waiting for replay to complete.
> > > >
> > > > Feedback welcome.
> > > >
> > >
> > > Here is the updated v2 patch set. Most of the updates are in patch 3.
> > >
> > > Changes from v1:
> > >
> > > Patch 1 (Extend wait types in xlogwait infra)
> > > - Renamed enum values for consistency (WAIT_LSN_TYPE_REPLAY →
> > > WAIT_LSN_TYPE_REPLAY_STANDBY, etc.)
> > >
> > > Patch 2 (pg_last_wal_write_lsn):
> > > - Clarified documentation and comment
> > > - Improved pg_proc.dat description
> > >
> > > Patch 3 (MODE parameter):
> > > - Replaced direct cast with explicit switch statement for WaitLSNMode
> > > → WaitLSNType conversion
> > > - Improved FLUSH/WRITE mode documentation with verification function references
> > > - TAP tests (7b, 7c, 7d): Added walreceiver control for concurrency,
> > > explicit blocking verification via poll_query_until, and log-based
> > > completion verification via wait_for_log
> > > - Fix the timing issue in wait for all three sessions to get the
> > > errors after promotion of tap test 8.
> > >
> > > --
> > > Best,
> > > Xuneng
> >
> > Here is the updated v3. The changes are made to patch 3:
> >
> > - Refactor duplicated TAP test code by extracting helper routines for
> > starting and stopping walreceiver.
> > - Increase the number of concurrent WRITE and FLUSH waiters in tests
> > 7b and 7c from three to five, matching the number in test 7a.
> >
> > --
> > Best,
> > Xuneng
>
> Just realized that patch 2 in prior emails could be dropped for
> simplicity. Since the write LSN can be retrieved directly from
> pg_stat_wal_receiver, the TAP test in patch 3 does not require a
> separate SQL function for this purpose alone.
>

Just rebase with minor changes to the wait-lsn types.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v5-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch (3.2K, ../../CABPTF7XbW0gSFMdhHq4suPhSaSTAHCHUp8twG4dwXHTGqQtNxA@mail.gmail.com/2-v5-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch)
  download | inline diff:
From 9b5e818ed2807a7c2eb3ac743cbf4dfe8103ea6d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v5 3/4] Add tab completion for WAIT FOR LSN MODE parameter

Update psql tab completion to support the optional MODE parameter in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords since MODE defaults to REPLAY.
---
 src/bin/psql/tab-complete.in.c | 39 ++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index b1ff6f6cd94..8f269b5cb13 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5327,10 +5327,11 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /*
- * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
+ * WAIT FOR LSN '<lsn>' [ MODE { REPLAY | FLUSH | WRITE } ] [ WITH ( option [, ...] ) ]
  * where option can be:
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * MODE defaults to REPLAY if not specified.
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5339,25 +5340,41 @@ match_previous_words(int pattern_id,
 	else if (Matches("WAIT", "FOR", "LSN"))
 		/* No completion for LSN value - user must provide manually */
 		;
+
+	/*
+	 * After LSN value, offer MODE (optional) or WITH, since MODE defaults to
+	 * REPLAY
+	 */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny))
+		COMPLETE_WITH("MODE", "WITH");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE"))
+		COMPLETE_WITH("REPLAY", "FLUSH", "WRITE");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny))
 		COMPLETE_WITH("WITH");
+	/* WITH directly after LSN (using default REPLAY mode) */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list (both with and without explicit MODE).
+	 * This fires when we're in an unfinished parenthesized option list.
+	 * get_previous_words treats a completed parenthesized option list as one
+	 * word, so the above test is correct. timeout takes a string value,
+	 * no_throw takes no value. We don't offer completions for these values.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
 			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+	}
+	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*") &&
+			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*)"))
+	{
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("timeout", "no_throw");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v5-0004-Use-WAIT-FOR-LSN-in.patch (3.1K, ../../CABPTF7XbW0gSFMdhHq4suPhSaSTAHCHUp8twG4dwXHTGqQtNxA@mail.gmail.com/3-v5-0004-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From dd82542b2a4961fd050eab70ea66a1c152edefdc Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v5 4/4] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 35 ++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..eec8233b515 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3335,6 +3335,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3347,9 +3350,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3367,6 +3367,35 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		# Map mode names to WAIT FOR LSN MODE values (uppercase)
+		my $wait_mode = uc($mode);
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' MODE ${wait_mode} WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v5-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (10.7K, ../../CABPTF7XbW0gSFMdhHq4suPhSaSTAHCHUp8twG4dwXHTGqQtNxA@mail.gmail.com/4-v5-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From 66c509e07bcbaa4580b32266326e34487a16d683 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v5 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 84 ++++++++++++++-----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 12 ++-
 6 files changed, 80 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6a5640df51a..a6e348f2109 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6241,7 +6241,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index ae2398d6975..01ffe30ffee 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1846,8 +1846,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..726a4a14084 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,48 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_COUNT:
+			break;
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +389,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_PRIMARY_FLUSH && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +422,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c0632bf901a..05bd4376c67 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..3b2f34b8698 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v5-0002-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch (39.2K, ../../CABPTF7XbW0gSFMdhHq4suPhSaSTAHCHUp8twG4dwXHTGqQtNxA@mail.gmail.com/5-v5-0002-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From ac01547201b1098c31e9bb46594896b677207bd8 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v5 2/4] Add MODE parameter to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE parameter that
specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]

- REPLAY (default): Wait for WAL to be replayed to the specified LSN
- WRITE: Wait for WAL to be written (received) to the specified LSN
- FLUSH: Wait for WAL to be flushed to disk at the specified LSN

The default mode is REPLAY, matching the original behavior when MODE
is not specified. This follows the pattern used by LOCK command where
the mode parameter is optional with a sensible default.

The WRITE and FLUSH modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and refactoring
  of existing WAIT FOR command documentation
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for WRITE/FLUSH waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 192 +++++++++++----
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  64 ++++-
 src/backend/parser/gram.y               |  21 +-
 src/backend/replication/walreceiver.c   |  19 ++
 src/include/nodes/parsenodes.h          |  11 +
 src/include/parser/kwlist.h             |   2 +
 src/test/recovery/t/049_wait_for_lsn.pl | 295 ++++++++++++++++++++++--
 8 files changed, 523 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..28c68678315 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,12 +16,13 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ MODE { REPLAY | FLUSH | WRITE } ]
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
@@ -34,20 +35,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -64,6 +67,61 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      Specifies the type of LSN processing to wait for. If not specified,
+      the default is <literal>REPLAY</literal>. The valid modes are:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term><literal>REPLAY</literal></term>
+       <listitem>
+        <para>
+         Wait for the LSN to be replayed (applied to the database).
+         After successful completion, <function>pg_last_wal_replay_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FLUSH</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and flushed to disk. This provides a durability guarantee
+         without waiting for the WAL to be applied. After successful
+         completion, <function>pg_last_wal_receive_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+         This value is also available as the <structfield>flushed_lsn</structfield>
+         column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+         <structname>pg_stat_wal_receiver</structname></link>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>WRITE</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and written to disk, but not yet flushed. This is faster
+         than <literal>FLUSH</literal> but provides weaker durability
+         guarantees since the data may still be in operating system buffers.
+         After successful completion, the <structfield>written_lsn</structfield>
+         column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+         <structname>pg_stat_wal_receiver</structname></link> will show
+         a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
     <listitem>
@@ -135,9 +193,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +209,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +244,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -198,9 +267,9 @@ pg_current_wal_insert_lsn
    changes made on primary should be guaranteed to be visible on replica.
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20';
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY;
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,21 +280,46 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+postgres=# WAIT FOR LSN '0/306EE20' MODE FLUSH;
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE WRITE WITH (TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '0.1s');
 ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
 </programlisting>
   </para>
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a6e348f2109..5c6f9feeccc 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6238,10 +6238,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..60cf3ee1c9a 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,12 +29,28 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType;
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
@@ -45,6 +62,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
 
+	/* Convert parse-time WaitLSNMode to runtime WaitLSNType */
+	switch (stmt->mode)
+	{
+		case WAIT_LSN_MODE_REPLAY:
+			lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			break;
+		case WAIT_LSN_MODE_WRITE:
+			lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			break;
+		case WAIT_LSN_MODE_FLUSH:
+			lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			break;
+		default:
+			elog(ERROR, "unrecognized wait mode: %d", stmt->mode);
+	}
+
 	foreach_node(DefElem, defel, stmt->options)
 	{
 		if (strcmp(defel->defname, "timeout") == 0)
@@ -107,8 +140,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +173,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +187,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +206,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..94a9e874699 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -641,6 +641,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
+%type <ival>	opt_wait_lsn_mode
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <boolean> opt_unique_null_treatment
@@ -732,7 +733,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
 	EXPRESSION EXTENSION EXTERNAL EXTRACT
 
-	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
+	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FLUSH FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -773,7 +774,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLAY REPLICA
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -16541,15 +16542,23 @@ xml_passing_mech:
  *****************************************************************************/
 
 WaitStmt:
-			WAIT FOR LSN_P Sconst opt_wait_with_clause
+			WAIT FOR LSN_P Sconst opt_wait_lsn_mode opt_wait_with_clause
 				{
 					WaitStmt *n = makeNode(WaitStmt);
 					n->lsn_literal = $4;
-					n->options = $5;
+					n->mode = $5;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
 			;
 
+opt_wait_lsn_mode:
+			MODE REPLAY			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			| MODE FLUSH		{ $$ = WAIT_LSN_MODE_FLUSH; }
+			| MODE WRITE		{ $$ = WAIT_LSN_MODE_WRITE; }
+			| /*EMPTY*/			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			;
+
 opt_wait_with_clause:
 			WITH '(' utility_option_list ')'		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -17989,6 +17998,7 @@ unreserved_keyword:
 			| FILTER
 			| FINALIZE
 			| FIRST_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FORMAT
@@ -18124,6 +18134,7 @@ unreserved_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESPECT_P
@@ -18578,6 +18589,7 @@ bare_label_keyword:
 			| FINALIZE
 			| FIRST_P
 			| FLOAT_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FOREIGN
@@ -18761,6 +18773,7 @@ bare_label_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESTART
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..e15c5645b9c 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0f..c4d9f03a6a5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4413,10 +4413,21 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+/*
+ * WaitLSNMode - MODE parameter for WAIT FOR command
+ */
+typedef enum WaitLSNMode
+{
+	WAIT_LSN_MODE_REPLAY,		/* Wait for LSN replay on standby */
+	WAIT_LSN_MODE_WRITE,		/* Wait for LSN write on standby */
+	WAIT_LSN_MODE_FLUSH			/* Wait for LSN flush on standby */
+}			WaitLSNMode;
+
 typedef struct WaitStmt
 {
 	NodeTag		type;
 	char	   *lsn_literal;	/* LSN string from grammar */
+	WaitLSNMode mode;			/* Wait mode: REPLAY/FLUSH/WRITE */
 	List	   *options;		/* List of DefElem nodes */
 } WaitStmt;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9fde58f541c..04008805e46 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -176,6 +176,7 @@ PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("flush", FLUSH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -379,6 +380,7 @@ PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("replay", REPLAY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD, AS_LABEL)
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..98060a5c79f 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -7,6 +7,38 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql('postgres',
+		"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '$saved_primary_conninfo';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +94,34 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' MODE WRITE WITH (timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR MODE WRITE");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' MODE FLUSH WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR MODE FLUSH");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +147,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +184,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +267,7 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -226,7 +285,9 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
 \$\$
 LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -239,10 +300,11 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->query_until(
 		qr/start/, qq[
 		\\echo start
-		WAIT FOR LSN '${lsn}';
+		WAIT FOR LSN '${lsn}' MODE REPLAY;
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +313,200 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# 7b. Check the scenario of multiple WRITE waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start WRITE waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' MODE WRITE WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'write_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start FLUSH waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' MODE FLUSH WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'flush_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  We stop the walreceiver and pause replay to
+# ensure all waiters block.  Then we resume replay and restart the
+# walreceiver to verify they unblock and complete correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('REPLAY', 'WRITE', 'FLUSH');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' MODE $mixed_modes[$i % 3] WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'mixed_done %', $i; END \$\$;
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
+
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('REPLAY', 'WRITE', 'FLUSH');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' MODE $wait_modes[$i];
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,17 +516,24 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
 
-$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+ok(1, 'promotion interrupted all wait modes');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}' MODE REPLAY;");
 
 ok(1, 'wait for already replayed LSN exits immediately even after promotion');
 
 $output = $node_standby->safe_psql(
 	'postgres', qq[
-	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+	WAIT FOR LSN '${lsn4}' MODE REPLAY WITH (timeout '10ms', no_throw);]);
 ok($output eq "not in recovery",
 	"WAIT FOR returns correct status after standby promotion");
 
@@ -295,8 +541,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-16 04:46                                                                                   ` Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-16 04:46 UTC (permalink / raw)
  To: pgsql-hackers <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Dec 16, 2025 at 11:28 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Tue, Dec 2, 2025 at 6:10 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Tue, Dec 2, 2025 at 11:08 AM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Mon, Dec 1, 2025 at 12:33 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi hackers,
> > > >
> > > > On Tue, Nov 25, 2025 at 7:51 PM Xuneng Zhou <[email protected]> wrote:
> > > > >
> > > > > Hi!
> > > > >
> > > > > > > > > At the moment, the WAIT FOR LSN command supports only the replay mode.
> > > > > > > > > If we intend to extend its functionality more broadly, one option is
> > > > > > > > > to add a mode option or something similar. Are users expected to wait
> > > > > > > > > for flush(or others) completion in such cases? If not, and the TAP
> > > > > > > > > test is the only intended use, this approach might be a bit of an
> > > > > > > > > overkill.
> > > > > > > >
> > > > > > > > I would say that adding mode parameter seems to be a pretty natural
> > > > > > > > extension of what we have at the moment.  I can imagine some
> > > > > > > > clustering solution can use it to wait for certain transaction to be
> > > > > > > > flushed at the replica (without delaying the commit at the primary).
> > > > > > > >
> > > > > > > > ------
> > > > > > > > Regards,
> > > > > > > > Alexander Korotkov
> > > > > > > > Supabase
> > > > > > >
> > > > > > > Makes sense. I'll play with it and try to prepare a follow-up patch.
> > > > > > >
> > > > > > > --
> > > > > > > Best,
> > > > > > > Xuneng
> > > > > >
> > > > > > In terms of extending the functionality of the command, I see two
> > > > > > possible approaches here. One is to keep mode as a mandatory keyword,
> > > > > > and the other is to introduce it as an option in the WITH clause.
> > > > > >
> > > > > > Syntax Option A: Mode in the WITH Clause
> > > > > >
> > > > > > WAIT FOR LSN '0/12345' WITH (mode = 'replay');
> > > > > > WAIT FOR LSN '0/12345' WITH (mode = 'flush');
> > > > > > WAIT FOR LSN '0/12345' WITH (mode = 'write');
> > > > > >
> > > > > > With this option, we can keep "replay" as the default mode. That means
> > > > > > existing TAP tests won’t need to be refactored unless they explicitly
> > > > > > want a different mode.
> > > > > >
> > > > > > Syntax Option B: Mode as Part of the Main Command
> > > > > >
> > > > > > WAIT FOR LSN '0/12345' MODE 'replay';
> > > > > > WAIT FOR LSN '0/12345' MODE 'flush';
> > > > > > WAIT FOR LSN '0/12345' MODE 'write';
> > > > > >
> > > > > > Or a more concise variant using keywords:
> > > > > >
> > > > > > WAIT FOR LSN '0/12345' REPLAY;
> > > > > > WAIT FOR LSN '0/12345' FLUSH;
> > > > > > WAIT FOR LSN '0/12345' WRITE;
> > > > > >
> > > > > > This option produces a cleaner syntax if the intent is simply to wait
> > > > > > for a particular LSN type, without specifying additional options like
> > > > > > timeout or no_throw.
> > > > > >
> > > > > > I don’t have a clear preference among them. I’d be interested to hear
> > > > > > what you or others think is the better direction.
> > > > > >
> > > > >
> > > > > I've implemented a patch that adds MODE support to WAIT FOR LSN
> > > > >
> > > > > The new grammar looks like:
> > > > >
> > > > > ——
> > > > > WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]
> > > > > ——
> > > > >
> > > > > Two modes added: flush and write
> > > > >
> > > > > Design decisions:
> > > > >
> > > > > 1. MODE as a separate keyword (not in WITH clause) - This follows the
> > > > > pattern used by LOCK command. It also makes the common case more
> > > > > concise.
> > > > >
> > > > > 2. REPLAY as the default - When MODE is not specified, it defaults to REPLAY.
> > > > >
> > > > > 3. Keywords rather than strings - Using `MODE WRITE` rather than `MODE 'write'`
> > > > >
> > > > > The patch set includes:
> > > > > -------
> > > > > 0001 - Extend xlogwait infrastructure with write and flush wait types
> > > > >
> > > > > Adds WAIT_LSN_TYPE_WRITE and WAIT_LSN_TYPE_FLUSH to WaitLSNType enum,
> > > > > along with corresponding wait events and pairing heaps. Introduces
> > > > > GetCurrentLSNForWaitType() to retrieve the appropriate LSN based on
> > > > > wait type, and adds wakeup calls in walreceiver for write/flush
> > > > > events.
> > > > >
> > > > > -------
> > > > > 0002 - Add pg_last_wal_write_lsn() SQL function
> > > > >
> > > > > Adds a new SQL function that returns the current WAL write position on
> > > > > a standby using GetWalRcvWriteRecPtr(). This complements existing
> > > > > pg_last_wal_receive_lsn() (flush) and pg_last_wal_replay_lsn()
> > > > > functions, enabling verification of WAIT FOR LSN MODE WRITE in TAP
> > > > > tests.
> > > > >
> > > > > -------
> > > > > 0003 - Add MODE parameter to WAIT FOR LSN command
> > > > >
> > > > > Extends the parser and executor to support the optional MODE
> > > > > parameter. Updates documentation with new syntax and mode
> > > > > descriptions. Adds TAP tests covering all three modes including
> > > > > mixed-mode concurrent waiters.
> > > > >
> > > > > -------
> > > > > 0004 - Add tab completion for WAIT FOR LSN MODE parameter
> > > > >
> > > > > Adds psql tab completion support: completes MODE after LSN value,
> > > > > completes REPLAY/WRITE/FLUSH after MODE keyword, and completes WITH
> > > > > after mode selection.
> > > > >
> > > > > -------
> > > > > 0005 - Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()
> > > > >
> > > > > Replaces polling-based wait_for_catchup() with WAIT FOR LSN when the
> > > > > target is a standby in recovery, improving test efficiency by avoiding
> > > > > repeated queries.
> > > > >
> > > > > The WRITE and FLUSH modes enable scenarios where applications need to
> > > > > ensure WAL has been received or persisted on the standby without
> > > > > waiting for replay to complete.
> > > > >
> > > > > Feedback welcome.
> > > > >
> > > >
> > > > Here is the updated v2 patch set. Most of the updates are in patch 3.
> > > >
> > > > Changes from v1:
> > > >
> > > > Patch 1 (Extend wait types in xlogwait infra)
> > > > - Renamed enum values for consistency (WAIT_LSN_TYPE_REPLAY →
> > > > WAIT_LSN_TYPE_REPLAY_STANDBY, etc.)
> > > >
> > > > Patch 2 (pg_last_wal_write_lsn):
> > > > - Clarified documentation and comment
> > > > - Improved pg_proc.dat description
> > > >
> > > > Patch 3 (MODE parameter):
> > > > - Replaced direct cast with explicit switch statement for WaitLSNMode
> > > > → WaitLSNType conversion
> > > > - Improved FLUSH/WRITE mode documentation with verification function references
> > > > - TAP tests (7b, 7c, 7d): Added walreceiver control for concurrency,
> > > > explicit blocking verification via poll_query_until, and log-based
> > > > completion verification via wait_for_log
> > > > - Fix the timing issue in wait for all three sessions to get the
> > > > errors after promotion of tap test 8.
> > > >
> > > > --
> > > > Best,
> > > > Xuneng
> > >
> > > Here is the updated v3. The changes are made to patch 3:
> > >
> > > - Refactor duplicated TAP test code by extracting helper routines for
> > > starting and stopping walreceiver.
> > > - Increase the number of concurrent WRITE and FLUSH waiters in tests
> > > 7b and 7c from three to five, matching the number in test 7a.
> > >
> > > --
> > > Best,
> > > Xuneng
> >
> > Just realized that patch 2 in prior emails could be dropped for
> > simplicity. Since the write LSN can be retrieved directly from
> > pg_stat_wal_receiver, the TAP test in patch 3 does not require a
> > separate SQL function for this purpose alone.
> >
>
> Just rebase with minor changes to the wait-lsn types.
>

Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
statement in v5 patch 1.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v6-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (10.6K, ../../CABPTF7XKti620ZAOXPGuhSZxCKyaV_9stq7ruhnuxvshUxCeRQ@mail.gmail.com/2-v6-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From 7292901a0119dca75c349cd6f5a460f5cb0e4139 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v6 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 81 ++++++++++++++-----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 12 ++-
 6 files changed, 77 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6a5640df51a..a6e348f2109 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6241,7 +6241,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index ae2398d6975..01ffe30ffee 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1846,8 +1846,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..d54b2fd7ae4 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,45 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +386,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_PRIMARY_FLUSH && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +419,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c0632bf901a..05bd4376c67 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..3b2f34b8698 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v6-0002-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch (39.2K, ../../CABPTF7XKti620ZAOXPGuhSZxCKyaV_9stq7ruhnuxvshUxCeRQ@mail.gmail.com/3-v6-0002-Add-MODE-parameter-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 0df07ec61ec10096782262d7fcb996e879cf2367 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v6 2/4] Add MODE parameter to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE parameter that
specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [MODE { REPLAY | WRITE | FLUSH }] [WITH (...)]

- REPLAY (default): Wait for WAL to be replayed to the specified LSN
- WRITE: Wait for WAL to be written (received) to the specified LSN
- FLUSH: Wait for WAL to be flushed to disk at the specified LSN

The default mode is REPLAY, matching the original behavior when MODE
is not specified. This follows the pattern used by LOCK command where
the mode parameter is optional with a sensible default.

The WRITE and FLUSH modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and refactoring
  of existing WAIT FOR command documentation
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for WRITE/FLUSH waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 192 +++++++++++----
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  64 ++++-
 src/backend/parser/gram.y               |  21 +-
 src/backend/replication/walreceiver.c   |  19 ++
 src/include/nodes/parsenodes.h          |  11 +
 src/include/parser/kwlist.h             |   2 +
 src/test/recovery/t/049_wait_for_lsn.pl | 295 ++++++++++++++++++++++--
 8 files changed, 523 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..28c68678315 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,12 +16,13 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ MODE { REPLAY | FLUSH | WRITE } ]
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
@@ -34,20 +35,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -64,6 +67,61 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      Specifies the type of LSN processing to wait for. If not specified,
+      the default is <literal>REPLAY</literal>. The valid modes are:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term><literal>REPLAY</literal></term>
+       <listitem>
+        <para>
+         Wait for the LSN to be replayed (applied to the database).
+         After successful completion, <function>pg_last_wal_replay_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FLUSH</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and flushed to disk. This provides a durability guarantee
+         without waiting for the WAL to be applied. After successful
+         completion, <function>pg_last_wal_receive_lsn()</function>
+         will return a value greater than or equal to the target LSN.
+         This value is also available as the <structfield>flushed_lsn</structfield>
+         column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+         <structname>pg_stat_wal_receiver</structname></link>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>WRITE</literal></term>
+       <listitem>
+        <para>
+         Wait for the WAL containing the LSN to be received from the
+         primary and written to disk, but not yet flushed. This is faster
+         than <literal>FLUSH</literal> but provides weaker durability
+         guarantees since the data may still be in operating system buffers.
+         After successful completion, the <structfield>written_lsn</structfield>
+         column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+         <structname>pg_stat_wal_receiver</structname></link> will show
+         a value greater than or equal to the target LSN.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>WITH ( <replaceable class="parameter">option</replaceable> [, ...] )</literal></term>
     <listitem>
@@ -135,9 +193,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +209,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +244,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -198,9 +267,9 @@ pg_current_wal_insert_lsn
    changes made on primary should be guaranteed to be visible on replica.
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20';
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY;
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,21 +280,46 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
 
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
+postgres=# WAIT FOR LSN '0/306EE20' MODE FLUSH;
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE WRITE WITH (TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '0.1s');
 ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60
 </programlisting>
   </para>
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
-postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
+postgres=# WAIT FOR LSN '0/306EE20' MODE REPLAY WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a6e348f2109..5c6f9feeccc 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6238,10 +6238,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..60cf3ee1c9a 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,12 +29,28 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType;
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
@@ -45,6 +62,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
 
+	/* Convert parse-time WaitLSNMode to runtime WaitLSNType */
+	switch (stmt->mode)
+	{
+		case WAIT_LSN_MODE_REPLAY:
+			lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			break;
+		case WAIT_LSN_MODE_WRITE:
+			lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			break;
+		case WAIT_LSN_MODE_FLUSH:
+			lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			break;
+		default:
+			elog(ERROR, "unrecognized wait mode: %d", stmt->mode);
+	}
+
 	foreach_node(DefElem, defel, stmt->options)
 	{
 		if (strcmp(defel->defname, "timeout") == 0)
@@ -107,8 +140,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +173,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +187,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +206,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..94a9e874699 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -641,6 +641,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
+%type <ival>	opt_wait_lsn_mode
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <boolean> opt_unique_null_treatment
@@ -732,7 +733,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
 	EXPRESSION EXTENSION EXTERNAL EXTRACT
 
-	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
+	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FLUSH FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -773,7 +774,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLAY REPLICA
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -16541,15 +16542,23 @@ xml_passing_mech:
  *****************************************************************************/
 
 WaitStmt:
-			WAIT FOR LSN_P Sconst opt_wait_with_clause
+			WAIT FOR LSN_P Sconst opt_wait_lsn_mode opt_wait_with_clause
 				{
 					WaitStmt *n = makeNode(WaitStmt);
 					n->lsn_literal = $4;
-					n->options = $5;
+					n->mode = $5;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
 			;
 
+opt_wait_lsn_mode:
+			MODE REPLAY			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			| MODE FLUSH		{ $$ = WAIT_LSN_MODE_FLUSH; }
+			| MODE WRITE		{ $$ = WAIT_LSN_MODE_WRITE; }
+			| /*EMPTY*/			{ $$ = WAIT_LSN_MODE_REPLAY; }
+			;
+
 opt_wait_with_clause:
 			WITH '(' utility_option_list ')'		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -17989,6 +17998,7 @@ unreserved_keyword:
 			| FILTER
 			| FINALIZE
 			| FIRST_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FORMAT
@@ -18124,6 +18134,7 @@ unreserved_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESPECT_P
@@ -18578,6 +18589,7 @@ bare_label_keyword:
 			| FINALIZE
 			| FIRST_P
 			| FLOAT_P
+			| FLUSH
 			| FOLLOWING
 			| FORCE
 			| FOREIGN
@@ -18761,6 +18773,7 @@ bare_label_keyword:
 			| RENAME
 			| REPEATABLE
 			| REPLACE
+			| REPLAY
 			| REPLICA
 			| RESET
 			| RESTART
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..e15c5645b9c 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0f..c4d9f03a6a5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4413,10 +4413,21 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+/*
+ * WaitLSNMode - MODE parameter for WAIT FOR command
+ */
+typedef enum WaitLSNMode
+{
+	WAIT_LSN_MODE_REPLAY,		/* Wait for LSN replay on standby */
+	WAIT_LSN_MODE_WRITE,		/* Wait for LSN write on standby */
+	WAIT_LSN_MODE_FLUSH			/* Wait for LSN flush on standby */
+}			WaitLSNMode;
+
 typedef struct WaitStmt
 {
 	NodeTag		type;
 	char	   *lsn_literal;	/* LSN string from grammar */
+	WaitLSNMode mode;			/* Wait mode: REPLAY/FLUSH/WRITE */
 	List	   *options;		/* List of DefElem nodes */
 } WaitStmt;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9fde58f541c..04008805e46 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -176,6 +176,7 @@ PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("flush", FLUSH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -379,6 +380,7 @@ PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("replay", REPLAY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD, AS_LABEL)
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..98060a5c79f 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -7,6 +7,38 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql('postgres',
+		"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '$saved_primary_conninfo';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +94,34 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' MODE WRITE WITH (timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR MODE WRITE");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' MODE FLUSH WITH (timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR MODE FLUSH");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +147,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +184,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +267,7 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -226,7 +285,9 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
 \$\$
 LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -239,10 +300,11 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->query_until(
 		qr/start/, qq[
 		\\echo start
-		WAIT FOR LSN '${lsn}';
+		WAIT FOR LSN '${lsn}' MODE REPLAY;
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +313,200 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# 7b. Check the scenario of multiple WRITE waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start WRITE waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' MODE WRITE WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'write_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start FLUSH waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' MODE FLUSH WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'flush_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  We stop the walreceiver and pause replay to
+# ensure all waiters block.  Then we resume replay and restart the
+# walreceiver to verify they unblock and complete correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('REPLAY', 'WRITE', 'FLUSH');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' MODE $mixed_modes[$i % 3] WITH (timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'mixed_done %', $i; END \$\$;
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
+
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('REPLAY', 'WRITE', 'FLUSH');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' MODE $wait_modes[$i];
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,17 +516,24 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
 
-$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
+ok(1, 'promotion interrupted all wait modes');
+
+$node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}' MODE REPLAY;");
 
 ok(1, 'wait for already replayed LSN exits immediately even after promotion');
 
 $output = $node_standby->safe_psql(
 	'postgres', qq[
-	WAIT FOR LSN '${lsn4}' WITH (timeout '10ms', no_throw);]);
+	WAIT FOR LSN '${lsn4}' MODE REPLAY WITH (timeout '10ms', no_throw);]);
 ok($output eq "not in recovery",
 	"WAIT FOR returns correct status after standby promotion");
 
@@ -295,8 +541,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



  [application/octet-stream] v6-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch (3.2K, ../../CABPTF7XKti620ZAOXPGuhSZxCKyaV_9stq7ruhnuxvshUxCeRQ@mail.gmail.com/4-v6-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-paramete.patch)
  download | inline diff:
From 94d36b07298fa2a46d26623c08a269cc6db6461a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v6 3/4] Add tab completion for WAIT FOR LSN MODE parameter

Update psql tab completion to support the optional MODE parameter in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords since MODE defaults to REPLAY.
---
 src/bin/psql/tab-complete.in.c | 39 ++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index b1ff6f6cd94..8f269b5cb13 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5327,10 +5327,11 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /*
- * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
+ * WAIT FOR LSN '<lsn>' [ MODE { REPLAY | FLUSH | WRITE } ] [ WITH ( option [, ...] ) ]
  * where option can be:
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * MODE defaults to REPLAY if not specified.
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5339,25 +5340,41 @@ match_previous_words(int pattern_id,
 	else if (Matches("WAIT", "FOR", "LSN"))
 		/* No completion for LSN value - user must provide manually */
 		;
+
+	/*
+	 * After LSN value, offer MODE (optional) or WITH, since MODE defaults to
+	 * REPLAY
+	 */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny))
+		COMPLETE_WITH("MODE", "WITH");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE"))
+		COMPLETE_WITH("REPLAY", "FLUSH", "WRITE");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny))
 		COMPLETE_WITH("WITH");
+	/* WITH directly after LSN (using default REPLAY mode) */
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list (both with and without explicit MODE).
+	 * This fires when we're in an unfinished parenthesized option list.
+	 * get_previous_words treats a completed parenthesized option list as one
+	 * word, so the above test is correct. timeout takes a string value,
+	 * no_throw takes no value. We don't offer completions for these values.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
 			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+	}
+	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*") &&
+			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "MODE", MatchAny, "WITH", "(*)"))
+	{
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("timeout", "no_throw");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v6-0004-Use-WAIT-FOR-LSN-in.patch (3.1K, ../../CABPTF7XKti620ZAOXPGuhSZxCKyaV_9stq7ruhnuxvshUxCeRQ@mail.gmail.com/5-v6-0004-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From 7265330a02c5d966ef42cce3f9c15f4acae37ff4 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v6 4/4] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 35 ++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..eec8233b515 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3335,6 +3335,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3347,9 +3350,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3367,6 +3367,35 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		# Map mode names to WAIT FOR LSN MODE values (uppercase)
+		my $wait_mode = uc($mode);
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' MODE ${wait_mode} WITH (timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-18 10:38                                                                                     ` Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-12-18 10:38 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi, Xuneng!

On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> statement in v5 patch 1.

Thank you for your work on this patchset.  Generally, it looks like
good and quite straightforward extension of the current functionality.
But this patch adds 4 new unreserved keywords to our grammar.  Do you
think we can put mode into with options clause?

------
Regards,
Alexander Korotkov





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-12-18 12:24                                                                                       ` Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-18 12:24 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> > Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> > statement in v5 patch 1.
>
> Thank you for your work on this patchset.  Generally, it looks like
> good and quite straightforward extension of the current functionality.
> But this patch adds 4 new unreserved keywords to our grammar.  Do you
> think we can put mode into with options clause?
>

Thanks for pointing this out. Yeah, 4 unreserved keywords add
complexity to the parser and it may not be worthwhile since replay is
expected to be the common use scenario. Maybe we can do something like
this:

-- Default (REPLAY mode)
WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');

-- Explicit REPLAY mode
WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');

-- WRITE mode
WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');

If no mode is set explicitly in the options clause, it defaults to
replay. I'll update the patch per your suggestion.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-18 12:25                                                                                         ` Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-12-18 12:25 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
> >
> > Hi, Xuneng!
> >
> > On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> > > Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> > > statement in v5 patch 1.
> >
> > Thank you for your work on this patchset.  Generally, it looks like
> > good and quite straightforward extension of the current functionality.
> > But this patch adds 4 new unreserved keywords to our grammar.  Do you
> > think we can put mode into with options clause?
> >
>
> Thanks for pointing this out. Yeah, 4 unreserved keywords add
> complexity to the parser and it may not be worthwhile since replay is
> expected to be the common use scenario. Maybe we can do something like
> this:
>
> -- Default (REPLAY mode)
> WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
>
> -- Explicit REPLAY mode
> WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
>
> -- WRITE mode
> WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
>
> If no mode is set explicitly in the options clause, it defaults to
> replay. I'll update the patch per your suggestion.

This is exactly what I meant.  Please, go ahead.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-12-19 02:49                                                                                           ` Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-19 02:49 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Thu, Dec 18, 2025 at 8:25 PM Alexander Korotkov <[email protected]> wrote:
>
> On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> > On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
> > >
> > > Hi, Xuneng!
> > >
> > > On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> > > > Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> > > > statement in v5 patch 1.
> > >
> > > Thank you for your work on this patchset.  Generally, it looks like
> > > good and quite straightforward extension of the current functionality.
> > > But this patch adds 4 new unreserved keywords to our grammar.  Do you
> > > think we can put mode into with options clause?
> > >
> >
> > Thanks for pointing this out. Yeah, 4 unreserved keywords add
> > complexity to the parser and it may not be worthwhile since replay is
> > expected to be the common use scenario. Maybe we can do something like
> > this:
> >
> > -- Default (REPLAY mode)
> > WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
> >
> > -- Explicit REPLAY mode
> > WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
> >
> > -- WRITE mode
> > WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
> >
> > If no mode is set explicitly in the options clause, it defaults to
> > replay. I'll update the patch per your suggestion.
>
> This is exactly what I meant.  Please, go ahead.
>

Here is the updated patch set (v7). Please check.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v7-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (10.9K, ../../CABPTF7V0xMVS=U-pdFADDe6WEoGJ7jWZt7pffXfnmePfLWjZjg@mail.gmail.com/2-v7-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From bbf69248589db7056b05ab996ec1831aa7fbb2b5 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v7 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 81 ++++++++++++++-----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 +++-
 6 files changed, 78 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6a5640df51a..a6e348f2109 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6241,7 +6241,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index ae2398d6975..01ffe30ffee 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1846,8 +1846,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..d54b2fd7ae4 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,45 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -341,13 +386,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (lsnType != WAIT_LSN_TYPE_PRIMARY_FLUSH && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +419,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index c0632bf901a..05bd4376c67 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..4cf13f0ccb3 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v7-0004-Use-WAIT-FOR-LSN-in.patch (3.0K, ../../CABPTF7V0xMVS=U-pdFADDe6WEoGJ7jWZt7pffXfnmePfLWjZjg@mail.gmail.com/3-v7-0004-Use-WAIT-FOR-LSN-in.patch)
  download | inline diff:
From 9dde4e330844d827f783ab2caca505036ac884b0 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v7 4/4] Use WAIT FOR LSN in 
 PostgreSQL::Test::Cluster::wait_for_catchup()

Replace polling-based catchup waiting with WAIT FOR LSN command when
running on a standby server. This is more efficient than repeatedly
querying pg_stat_replication as the WAIT FOR command uses the latch-
based wakeup mechanism.

The optimization applies when:
- The node is in recovery (standby server)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

For 'sent' mode or when running on a primary, the function falls back
to the original polling approach since WAIT FOR LSN is only available
during recovery.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 33 +++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..276350c5f13 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3335,6 +3335,9 @@ sub wait_for_catchup
 	$mode = defined($mode) ? $mode : 'replay';
 	my %valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3347,9 +3350,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
 		if ($isrecovery eq 't')
 		{
 			$target_lsn = $self->lsn('replay');
@@ -3367,6 +3367,33 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN when in recovery for supported modes (replay, write, flush)
+	# This is more efficient than polling pg_stat_replication
+	if (($mode ne 'sent') && ($isrecovery eq 't'))
+	{
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		my $query =
+		  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${mode}', timeout '${timeout}s', no_throw);];
+		my $output = $self->safe_psql('postgres', $query);
+		chomp($output);
+
+		if ($output ne 'success')
+		{
+			# Fetch additional detail for debugging purposes
+			$query = qq[SELECT * FROM pg_catalog.pg_stat_replication];
+			my $details = $self->safe_psql('postgres', $query);
+			diag qq(WAIT FOR LSN failed with status:
+${output});
+			diag qq(Last pg_stat_replication contents:
+${details});
+			croak "failed waiting for catchup";
+		}
+		print "done\n";
+		return;
+	}
+
+	# Polling for 'sent' mode or when not in recovery (WAIT FOR LSN not applicable)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v7-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch (2.3K, ../../CABPTF7V0xMVS=U-pdFADDe6WEoGJ7jWZt7pffXfnmePfLWjZjg@mail.gmail.com/4-v7-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch)
  download | inline diff:
From 62db341638bd9515584f9c24b0adfeec61ada252 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v7 3/4] Add tab completion for WAIT FOR LSN MODE option

Update psql tab completion to support the MODE option in WAIT FOR LSN
command's WITH clause. After typing 'mode' inside the parenthesized
option list, completion offers the valid mode values: 'replay', 'write',
and 'flush'.
---
 src/bin/psql/tab-complete.in.c | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index b1ff6f6cd94..5cb8de14e8e 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5329,8 +5329,10 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be: replay | write | flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5343,21 +5345,21 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.  mode takes a string value ('replay', 'write', 'flush'),
+	 * timeout takes a string value, no_throw takes no value.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'replay'", "'write'", "'flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v7-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch (34.8K, ../../CABPTF7V0xMVS=U-pdFADDe6WEoGJ7jWZt7pffXfnmePfLWjZjg@mail.gmail.com/5-v7-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 5136083fff62902515c82118250034e0ab75cf2f Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v7 2/4] Add MODE option to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE option in the
WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
- 'replay' (default): Wait for WAL to be replayed to the specified LSN
- 'write': Wait for WAL to be written (received) to the specified LSN
- 'flush': Wait for WAL to be flushed to disk at the specified LSN

The default mode is 'replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands where options are specified as string values in the WITH clause.

The 'write' and 'flush' modes are useful for scenarios where applications
need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete.

Also includes:
- Documentation updates for the new syntax and small refactoring for the existing ones
- Test coverage for all three modes including mixed concurrent waiters
- Wakeup logic in walreceiver for write/flush waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 182 ++++++++++----
 src/backend/access/transam/xlog.c       |   6 +-
 src/backend/commands/wait.c             |  74 +++++-
 src/backend/replication/walreceiver.c   |  19 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 305 ++++++++++++++++++++++--
 5 files changed, 508 insertions(+), 78 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..122012f5613 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym> on a replica</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    replay | write | flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,22 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+   On timeout, or if the server is promoted before
+   <parameter>lsn</parameter> is reached, an error is emitted,
+   unless <literal>NO_THROW</literal> is specified in the WITH clause.
+   If <parameter>NO_THROW</parameter> is specified, then the command
+   doesn't throw errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +80,52 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>replay</literal>: Wait for the LSN to be replayed
+            (applied to the database). After successful completion,
+            <function>pg_last_wal_replay_lsn()</function> will return a
+            value greater than or equal to the target LSN.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>flush</literal>: Wait for the WAL containing the LSN
+            to be received from the primary and flushed to disk. This
+            provides a durability guarantee without waiting for the WAL
+            to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>write</literal>: Wait for the WAL containing the LSN
+            to be received from the primary and written to disk, but not
+            yet flushed. This is faster than <literal>flush</literal> but
+            provides weaker durability guarantees since the data may still
+            be in operating system buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN.
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +189,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +205,33 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>REPLAY</literal> mode waits
+   for the LSN to be replayed (applied to the database), which is useful
+   to achieve read-your-writes consistency while using an async replica
+   for reads and the primary for writes. The <literal>FLUSH</literal> mode
+   waits for the WAL to be flushed to durable storage on the replica,
+   providing a durability guarantee without waiting for replay. The
+   <literal>WRITE</literal> mode waits for the WAL to be written to the
+   operating system, which is faster than flush but provides weaker
+   durability guarantees. In all cases, the <acronym>LSN</acronym> of the
+   last modification should be stored on the client application side or
+   the connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   <command>WAIT FOR</command> should be called on a standby.
+   If a user runs <command>WAIT FOR</command> on the primary, it
+   will error out unless <parameter>NO_THROW</parameter> is specified
+   in the WITH clause. However, if <command>WAIT FOR</command> is
+   called on a primary promoted from standby and <literal>lsn</literal>
+   was already reached, then the <command>WAIT FOR</command> command
+   just exits immediately. If the replica is promoted while waiting,
+   the command will return <literal>not in recovery</literal> (or throw
+   an error if <literal>NO_THROW</literal> is not specified). Promotion
+   creates a new timeline, and the LSN being waited for may refer to
+   WAL from the old timeline.
   </para>
 
 </refsect1>
@@ -175,21 +240,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +265,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +276,31 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +310,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a6e348f2109..5c6f9feeccc 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6238,10 +6238,12 @@ StartupXLOG(void)
 	LWLockRelease(ControlFileLock);
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..b3f1f7b8a69 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,18 +29,35 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* "replay", "flush", "write" */
+	const char *verb;			/* "replayed", "flushed", "written" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +65,30 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
+								"MODE", mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +148,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +181,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +195,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +214,26 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..e15c5645b9c 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1014,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for then walk over
+		 * the shared memory array and set latches to notify the waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..b589cecc028 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,4 +1,4 @@
-# Checks waiting for the LSN replay on standby using
+# Checks waiting for the LSN replay/write/flush on standby using
 # the WAIT FOR command.
 use strict;
 use warnings FATAL => 'all';
@@ -7,6 +7,38 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql('postgres',
+		"SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'");
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '$saved_primary_conninfo';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +94,34 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with WRITE and FLUSH modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'write'");
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok((split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'flush'");
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,7 +147,7 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
+# 5. Check that WAIT FOR triggers an error if called on primary,
 # within another function, or inside a transaction with an isolation level
 # higher than READ COMMITTED.
 
@@ -125,7 +184,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,7 +267,23 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "MODE": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'replay', MODE 'write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple REPLAY waiters.  We make 5 background
 # psql sessions each waiting for a corresponding insertion.  When waiting is
 # finished, stored procedures logs if there are visible as many rows as
 # should be.
@@ -226,7 +301,9 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
 \$\$
 LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +320,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +329,200 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple REPLAY waiters reported consistent data');
+
+# 7b. Check the scenario of multiple WRITE waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start WRITE waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'write', timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'write_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple WRITE waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple FLUSH waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start FLUSH waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'flush', timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'flush_done %', $i; END \$\$;
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple FLUSH waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed mode waiters (REPLAY, WRITE, FLUSH)
+# running concurrently.  We start 6 sessions: 2 for each mode, all waiting
+# for the same target LSN.  We stop the walreceiver and pause replay to
+# ensure all waiters block.  Then we resume replay and restart the
+# walreceiver to verify they unblock and complete correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('replay', 'write', 'flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		DO \$\$ BEGIN RAISE LOG 'mixed_done %', $i; END \$\$;
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
+
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 8. Check that the standby promotion terminates all wait modes.  Start
+# waiting for unreachable LSNs with REPLAY, WRITE, and FLUSH modes, then
+# promote.  Check the log for the relevant error messages.  Also, check that
+# waiting for already replayed LSN doesn't cause an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('replay', 'write', 'flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +532,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +557,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-20 21:09                                                                                             ` Alexander Korotkov <[email protected]>
  2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-12-20 21:09 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Fri, Dec 19, 2025 at 4:50 AM Xuneng Zhou <[email protected]> wrote:
> On Thu, Dec 18, 2025 at 8:25 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> > > On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
> > > >
> > > > Hi, Xuneng!
> > > >
> > > > On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> > > > > Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> > > > > statement in v5 patch 1.
> > > >
> > > > Thank you for your work on this patchset.  Generally, it looks like
> > > > good and quite straightforward extension of the current functionality.
> > > > But this patch adds 4 new unreserved keywords to our grammar.  Do you
> > > > think we can put mode into with options clause?
> > > >
> > >
> > > Thanks for pointing this out. Yeah, 4 unreserved keywords add
> > > complexity to the parser and it may not be worthwhile since replay is
> > > expected to be the common use scenario. Maybe we can do something like
> > > this:
> > >
> > > -- Default (REPLAY mode)
> > > WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
> > >
> > > -- Explicit REPLAY mode
> > > WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
> > >
> > > -- WRITE mode
> > > WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
> > >
> > > If no mode is set explicitly in the options clause, it defaults to
> > > replay. I'll update the patch per your suggestion.
> >
> > This is exactly what I meant.  Please, go ahead.
> >
>
> Here is the updated patch set (v7). Please check.

I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
mode parameter.  Should we allow this?

If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
principle, we could encode both as just 'flush' mode, and detect which
WaitLSNType to pick by checking if recovery is in progress.  However,
how should we then react to unreached flush location after standby
promotion (technically it could be still reached but on the different
timeline)?

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-12-21 04:37                                                                                               ` Xuneng Zhou <[email protected]>
  2025-12-22 07:56                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-21 04:37 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

Thanks for your feedback!

> I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
> mode parameter.  Should we allow this?

I think this constraint could be relaxed if needed. I was previously
unsure about the use cases.

> If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
> separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
> principle, we could encode both as just 'flush' mode, and detect which
> WaitLSNType to pick by checking if recovery is in progress.  However,
> how should we then react to unreached flush location after standby
> promotion (technically it could be still reached but on the different
> timeline)?
>

Technically, we can use 'flush' mode to specify WAIT FOR behavior in
both primary and replica. Currently, wait for commands error out if
promotion occurs since: either the requested LSN type does not exist
on the primary, or we do not yet have the infrastructure to support
continuing the wait. If we allow waiting for flush on the primary as a
user-visible command and the wake-up calls for flush in primary are
introduced, the question becomes whether we should still abort the
wait on promotion, or continue waiting—as you noted—given that the
target LSN might still be reached, albeit on a different timeline. The
question behind this might be: do users care and should be aware of
the state change of the server while waiting? If they do, then we
better stop the waiting and report the error. In this case, I am
inclined to to break the unified flush mode to something like
primary_flush/standby_flush mode and
WAIT_LSN_TYPE_PRIMARY_FLUSH/WAIT_LSN_TYPE_STANDBY_FLUSH respectively.

--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-22 07:56                                                                                                 ` Xuneng Zhou <[email protected]>
  2025-12-25 11:13                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-22 07:56 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Sun, Dec 21, 2025 at 12:37 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi Alexander,
>
> Thanks for your feedback!
>
> > I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
> > mode parameter.  Should we allow this?
>
> I think this constraint could be relaxed if needed. I was previously
> unsure about the use cases.

Flush mode on the primary seems useful when synchronous_commit is set
to off [1]. In that mode, a transaction in primary may return success
before its WAL is durably flushed to disk, trading durability for
lower latency. A “wait for primary flush” operation provides an
explicit durability barrier for cases where applications or tools
occasionally need stronger guarantees.

[1] https://postgresqlco.nf/doc/en/param/synchronous_commit/

> > If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
> > separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
> > principle, we could encode both as just 'flush' mode, and detect which
> > WaitLSNType to pick by checking if recovery is in progress.  However,
> > how should we then react to unreached flush location after standby
> > promotion (technically it could be still reached but on the different
> > timeline)?
> >
>
> Technically, we can use 'flush' mode to specify WAIT FOR behavior in
> both primary and replica. Currently, wait for commands error out if
> promotion occurs since: either the requested LSN type does not exist
> on the primary, or we do not yet have the infrastructure to support
> continuing the wait. If we allow waiting for flush on the primary as a
> user-visible command and the wake-up calls for flush in primary are
> introduced, the question becomes whether we should still abort the
> wait on promotion, or continue waiting—as you noted—given that the
> target LSN might still be reached, albeit on a different timeline. The
> question behind this might be: do users care and should be aware of
> the state change of the server while waiting? If they do, then we
> better stop the waiting and report the error. In this case, I am
> inclined to to break the unified flush mode to something like
> primary_flush/standby_flush mode and
> WAIT_LSN_TYPE_PRIMARY_FLUSH/WAIT_LSN_TYPE_STANDBY_FLUSH respectively.
>

After further consideration, it also seems reasonable to use a single,
unified flush mode that works on both primary and standby servers,
provided its semantics are clearly documented to avoid the potential
confusion on failure. I don’t have a strong preference between these
two and would be interested in your thoughts.

If a standby is promoted while a session is waiting, the command
better abort and return an error (or report “not in recovery” when
using NO_THROW). At that point, the meaning of the LSN being waited
for may have changed due to the timeline switch and the transition
from standby to primary. An LSN such as 0/5000000 on TLI 2 can
represent entirely different WAL content from 0/5000000 on TLI 1.
Allowing the wait to silently continue across promotion risks giving
users a false sense of safety—for example, interpreting “wait
completed” as “the original data is now durable,” which would no
longer be true.

--
Best,
Xuneng


Attachments:

  [image/png] synchronous_commit.png (256.1K, ../../CABPTF7WJf5BnG1yCVk032+QiGuCmrhgnfNFO2HTgcuWAeRD9+A@mail.gmail.com/2-synchronous_commit.png)
  download | view image

^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-22 07:56                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-25 11:13                                                                                                   ` Alexander Korotkov <[email protected]>
  2025-12-25 12:52                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-12-25 11:13 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi, Xuneng!

On Mon, Dec 22, 2025 at 9:57 AM Xuneng Zhou <[email protected]> wrote:
>
> On Sun, Dec 21, 2025 at 12:37 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi Alexander,
> >
> > Thanks for your feedback!
> >
> > > I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
> > > mode parameter.  Should we allow this?
> >
> > I think this constraint could be relaxed if needed. I was previously
> > unsure about the use cases.
>
> Flush mode on the primary seems useful when synchronous_commit is set
> to off [1]. In that mode, a transaction in primary may return success
> before its WAL is durably flushed to disk, trading durability for
> lower latency. A “wait for primary flush” operation provides an
> explicit durability barrier for cases where applications or tools
> occasionally need stronger guarantees.
>
> [1] https://postgresqlco.nf/doc/en/param/synchronous_commit/
>
> > > If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
> > > separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
> > > principle, we could encode both as just 'flush' mode, and detect which
> > > WaitLSNType to pick by checking if recovery is in progress.  However,
> > > how should we then react to unreached flush location after standby
> > > promotion (technically it could be still reached but on the different
> > > timeline)?
> > >
> >
> > Technically, we can use 'flush' mode to specify WAIT FOR behavior in
> > both primary and replica. Currently, wait for commands error out if
> > promotion occurs since: either the requested LSN type does not exist
> > on the primary, or we do not yet have the infrastructure to support
> > continuing the wait. If we allow waiting for flush on the primary as a
> > user-visible command and the wake-up calls for flush in primary are
> > introduced, the question becomes whether we should still abort the
> > wait on promotion, or continue waiting—as you noted—given that the
> > target LSN might still be reached, albeit on a different timeline. The
> > question behind this might be: do users care and should be aware of
> > the state change of the server while waiting? If they do, then we
> > better stop the waiting and report the error. In this case, I am
> > inclined to to break the unified flush mode to something like
> > primary_flush/standby_flush mode and
> > WAIT_LSN_TYPE_PRIMARY_FLUSH/WAIT_LSN_TYPE_STANDBY_FLUSH respectively.
> >
>
> After further consideration, it also seems reasonable to use a single,
> unified flush mode that works on both primary and standby servers,
> provided its semantics are clearly documented to avoid the potential
> confusion on failure. I don’t have a strong preference between these
> two and would be interested in your thoughts.
>
> If a standby is promoted while a session is waiting, the command
> better abort and return an error (or report “not in recovery” when
> using NO_THROW). At that point, the meaning of the LSN being waited
> for may have changed due to the timeline switch and the transition
> from standby to primary. An LSN such as 0/5000000 on TLI 2 can
> represent entirely different WAL content from 0/5000000 on TLI 1.
> Allowing the wait to silently continue across promotion risks giving
> users a false sense of safety—for example, interpreting “wait
> completed” as “the original data is now durable,” which would no
> longer be true.

Agree, but there is still risk that promotion happens after user send
the query but before we started to wait.  In this case we will still
silently start to wait on primary, while user probably meant to wait
on replica.  Probably it would be safer to have separate user-visible
modes for waiting on primary and on replica?

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-22 07:56                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-25 11:13                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-12-25 12:52                                                                                                     ` Xuneng Zhou <[email protected]>
  2025-12-25 16:34                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-25 12:52 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Thu, Dec 25, 2025 at 7:13 PM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> On Mon, Dec 22, 2025 at 9:57 AM Xuneng Zhou <[email protected]> wrote:
> >
> > On Sun, Dec 21, 2025 at 12:37 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi Alexander,
> > >
> > > Thanks for your feedback!
> > >
> > > > I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
> > > > mode parameter.  Should we allow this?
> > >
> > > I think this constraint could be relaxed if needed. I was previously
> > > unsure about the use cases.
> >
> > Flush mode on the primary seems useful when synchronous_commit is set
> > to off [1]. In that mode, a transaction in primary may return success
> > before its WAL is durably flushed to disk, trading durability for
> > lower latency. A “wait for primary flush” operation provides an
> > explicit durability barrier for cases where applications or tools
> > occasionally need stronger guarantees.
> >
> > [1] https://postgresqlco.nf/doc/en/param/synchronous_commit/
> >
> > > > If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
> > > > separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
> > > > principle, we could encode both as just 'flush' mode, and detect which
> > > > WaitLSNType to pick by checking if recovery is in progress.  However,
> > > > how should we then react to unreached flush location after standby
> > > > promotion (technically it could be still reached but on the different
> > > > timeline)?
> > > >
> > >
> > > Technically, we can use 'flush' mode to specify WAIT FOR behavior in
> > > both primary and replica. Currently, wait for commands error out if
> > > promotion occurs since: either the requested LSN type does not exist
> > > on the primary, or we do not yet have the infrastructure to support
> > > continuing the wait. If we allow waiting for flush on the primary as a
> > > user-visible command and the wake-up calls for flush in primary are
> > > introduced, the question becomes whether we should still abort the
> > > wait on promotion, or continue waiting—as you noted—given that the
> > > target LSN might still be reached, albeit on a different timeline. The
> > > question behind this might be: do users care and should be aware of
> > > the state change of the server while waiting? If they do, then we
> > > better stop the waiting and report the error. In this case, I am
> > > inclined to to break the unified flush mode to something like
> > > primary_flush/standby_flush mode and
> > > WAIT_LSN_TYPE_PRIMARY_FLUSH/WAIT_LSN_TYPE_STANDBY_FLUSH respectively.
> > >
> >
> > After further consideration, it also seems reasonable to use a single,
> > unified flush mode that works on both primary and standby servers,
> > provided its semantics are clearly documented to avoid the potential
> > confusion on failure. I don’t have a strong preference between these
> > two and would be interested in your thoughts.
> >
> > If a standby is promoted while a session is waiting, the command
> > better abort and return an error (or report “not in recovery” when
> > using NO_THROW). At that point, the meaning of the LSN being waited
> > for may have changed due to the timeline switch and the transition
> > from standby to primary. An LSN such as 0/5000000 on TLI 2 can
> > represent entirely different WAL content from 0/5000000 on TLI 1.
> > Allowing the wait to silently continue across promotion risks giving
> > users a false sense of safety—for example, interpreting “wait
> > completed” as “the original data is now durable,” which would no
> > longer be true.
>
> Agree, but there is still risk that promotion happens after user send
> the query but before we started to wait.  In this case we will still
> silently start to wait on primary, while user probably meant to wait
> on replica.  Probably it would be safer to have separate user-visible
> modes for waiting on primary and on replica?
>

Thanks for your thoughts. You're right about the race condition. If
promotion happens between query submission and execution, a unified
'flush' mode could silently switch semantics without the user knowing.
Separate modes like 'standby_flush' and 'primary_flush' would make
user intent explicit and catch this case with an error, which is
safer. Do these two terms look reasonable to you, or would you suggest
better names? If they look ok, I plan to update the implementation to
use these two modes.


--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-22 07:56                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-25 11:13                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-25 12:52                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-25 16:34                                                                                                       ` Alexander Korotkov <[email protected]>
  2025-12-26 00:31                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-12-25 16:34 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Dec 25, 2025 at 2:52 PM Xuneng Zhou <[email protected]> wrote:
> On Thu, Dec 25, 2025 at 7:13 PM Alexander Korotkov <[email protected]> wrote:
> >
> > Hi, Xuneng!
> >
> > On Mon, Dec 22, 2025 at 9:57 AM Xuneng Zhou <[email protected]> wrote:
> > >
> > > On Sun, Dec 21, 2025 at 12:37 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi Alexander,
> > > >
> > > > Thanks for your feedback!
> > > >
> > > > > I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
> > > > > mode parameter.  Should we allow this?
> > > >
> > > > I think this constraint could be relaxed if needed. I was previously
> > > > unsure about the use cases.
> > >
> > > Flush mode on the primary seems useful when synchronous_commit is set
> > > to off [1]. In that mode, a transaction in primary may return success
> > > before its WAL is durably flushed to disk, trading durability for
> > > lower latency. A “wait for primary flush” operation provides an
> > > explicit durability barrier for cases where applications or tools
> > > occasionally need stronger guarantees.
> > >
> > > [1] https://postgresqlco.nf/doc/en/param/synchronous_commit/
> > >
> > > > > If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
> > > > > separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
> > > > > principle, we could encode both as just 'flush' mode, and detect which
> > > > > WaitLSNType to pick by checking if recovery is in progress.  However,
> > > > > how should we then react to unreached flush location after standby
> > > > > promotion (technically it could be still reached but on the different
> > > > > timeline)?
> > > > >
> > > >
> > > > Technically, we can use 'flush' mode to specify WAIT FOR behavior in
> > > > both primary and replica. Currently, wait for commands error out if
> > > > promotion occurs since: either the requested LSN type does not exist
> > > > on the primary, or we do not yet have the infrastructure to support
> > > > continuing the wait. If we allow waiting for flush on the primary as a
> > > > user-visible command and the wake-up calls for flush in primary are
> > > > introduced, the question becomes whether we should still abort the
> > > > wait on promotion, or continue waiting—as you noted—given that the
> > > > target LSN might still be reached, albeit on a different timeline. The
> > > > question behind this might be: do users care and should be aware of
> > > > the state change of the server while waiting? If they do, then we
> > > > better stop the waiting and report the error. In this case, I am
> > > > inclined to to break the unified flush mode to something like
> > > > primary_flush/standby_flush mode and
> > > > WAIT_LSN_TYPE_PRIMARY_FLUSH/WAIT_LSN_TYPE_STANDBY_FLUSH respectively.
> > > >
> > >
> > > After further consideration, it also seems reasonable to use a single,
> > > unified flush mode that works on both primary and standby servers,
> > > provided its semantics are clearly documented to avoid the potential
> > > confusion on failure. I don’t have a strong preference between these
> > > two and would be interested in your thoughts.
> > >
> > > If a standby is promoted while a session is waiting, the command
> > > better abort and return an error (or report “not in recovery” when
> > > using NO_THROW). At that point, the meaning of the LSN being waited
> > > for may have changed due to the timeline switch and the transition
> > > from standby to primary. An LSN such as 0/5000000 on TLI 2 can
> > > represent entirely different WAL content from 0/5000000 on TLI 1.
> > > Allowing the wait to silently continue across promotion risks giving
> > > users a false sense of safety—for example, interpreting “wait
> > > completed” as “the original data is now durable,” which would no
> > > longer be true.
> >
> > Agree, but there is still risk that promotion happens after user send
> > the query but before we started to wait.  In this case we will still
> > silently start to wait on primary, while user probably meant to wait
> > on replica.  Probably it would be safer to have separate user-visible
> > modes for waiting on primary and on replica?
> >
>
> Thanks for your thoughts. You're right about the race condition. If
> promotion happens between query submission and execution, a unified
> 'flush' mode could silently switch semantics without the user knowing.
> Separate modes like 'standby_flush' and 'primary_flush' would make
> user intent explicit and catch this case with an error, which is
> safer. Do these two terms look reasonable to you, or would you suggest
> better names? If they look ok, I plan to update the implementation to
> use these two modes.

Thank you, Xuneng.  'standby_flush' and 'primary_flush' look good for
me.  Please, go ahead.  I think we should name other modes
'standby_write' and 'standby_replay' for the sake of unity.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-22 07:56                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-25 11:13                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-25 12:52                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-25 16:34                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-12-26 00:31                                                                                                         ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-26 00:31 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Fri, Dec 26, 2025 at 12:34 AM Alexander Korotkov
<[email protected]> wrote:
>
> On Thu, Dec 25, 2025 at 2:52 PM Xuneng Zhou <[email protected]> wrote:
> > On Thu, Dec 25, 2025 at 7:13 PM Alexander Korotkov <[email protected]> wrote:
> > >
> > > Hi, Xuneng!
> > >
> > > On Mon, Dec 22, 2025 at 9:57 AM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > On Sun, Dec 21, 2025 at 12:37 PM Xuneng Zhou <[email protected]> wrote:
> > > > >
> > > > > Hi Alexander,
> > > > >
> > > > > Thanks for your feedback!
> > > > >
> > > > > > I see that we can't specify WAIT_LSN_TYPE_PRIMARY_FLUSH by setting
> > > > > > mode parameter.  Should we allow this?
> > > > >
> > > > > I think this constraint could be relaxed if needed. I was previously
> > > > > unsure about the use cases.
> > > >
> > > > Flush mode on the primary seems useful when synchronous_commit is set
> > > > to off [1]. In that mode, a transaction in primary may return success
> > > > before its WAL is durably flushed to disk, trading durability for
> > > > lower latency. A “wait for primary flush” operation provides an
> > > > explicit durability barrier for cases where applications or tools
> > > > occasionally need stronger guarantees.
> > > >
> > > > [1] https://postgresqlco.nf/doc/en/param/synchronous_commit/
> > > >
> > > > > > If we allow specifying WAIT_LSN_TYPE_PRIMARY_FLUSH, should it be
> > > > > > separate mode value or the same with WAIT_LSN_TYPE_STANDBY_FLUSH?  In
> > > > > > principle, we could encode both as just 'flush' mode, and detect which
> > > > > > WaitLSNType to pick by checking if recovery is in progress.  However,
> > > > > > how should we then react to unreached flush location after standby
> > > > > > promotion (technically it could be still reached but on the different
> > > > > > timeline)?
> > > > > >
> > > > >
> > > > > Technically, we can use 'flush' mode to specify WAIT FOR behavior in
> > > > > both primary and replica. Currently, wait for commands error out if
> > > > > promotion occurs since: either the requested LSN type does not exist
> > > > > on the primary, or we do not yet have the infrastructure to support
> > > > > continuing the wait. If we allow waiting for flush on the primary as a
> > > > > user-visible command and the wake-up calls for flush in primary are
> > > > > introduced, the question becomes whether we should still abort the
> > > > > wait on promotion, or continue waiting—as you noted—given that the
> > > > > target LSN might still be reached, albeit on a different timeline. The
> > > > > question behind this might be: do users care and should be aware of
> > > > > the state change of the server while waiting? If they do, then we
> > > > > better stop the waiting and report the error. In this case, I am
> > > > > inclined to to break the unified flush mode to something like
> > > > > primary_flush/standby_flush mode and
> > > > > WAIT_LSN_TYPE_PRIMARY_FLUSH/WAIT_LSN_TYPE_STANDBY_FLUSH respectively.
> > > > >
> > > >
> > > > After further consideration, it also seems reasonable to use a single,
> > > > unified flush mode that works on both primary and standby servers,
> > > > provided its semantics are clearly documented to avoid the potential
> > > > confusion on failure. I don’t have a strong preference between these
> > > > two and would be interested in your thoughts.
> > > >
> > > > If a standby is promoted while a session is waiting, the command
> > > > better abort and return an error (or report “not in recovery” when
> > > > using NO_THROW). At that point, the meaning of the LSN being waited
> > > > for may have changed due to the timeline switch and the transition
> > > > from standby to primary. An LSN such as 0/5000000 on TLI 2 can
> > > > represent entirely different WAL content from 0/5000000 on TLI 1.
> > > > Allowing the wait to silently continue across promotion risks giving
> > > > users a false sense of safety—for example, interpreting “wait
> > > > completed” as “the original data is now durable,” which would no
> > > > longer be true.
> > >
> > > Agree, but there is still risk that promotion happens after user send
> > > the query but before we started to wait.  In this case we will still
> > > silently start to wait on primary, while user probably meant to wait
> > > on replica.  Probably it would be safer to have separate user-visible
> > > modes for waiting on primary and on replica?
> > >
> >
> > Thanks for your thoughts. You're right about the race condition. If
> > promotion happens between query submission and execution, a unified
> > 'flush' mode could silently switch semantics without the user knowing.
> > Separate modes like 'standby_flush' and 'primary_flush' would make
> > user intent explicit and catch this case with an error, which is
> > safer. Do these two terms look reasonable to you, or would you suggest
> > better names? If they look ok, I plan to update the implementation to
> > use these two modes.
>
> Thank you, Xuneng.  'standby_flush' and 'primary_flush' look good for
> me.  Please, go ahead.  I think we should name other modes
> 'standby_write' and 'standby_replay' for the sake of unity.
>

Thanks. Yeah, renaming existing modes to  'standby_write' and
'standby_replay' also makes sense to me.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-26 08:24                                                                                             ` Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Chao Li @ 2025-12-26 08:24 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>



> On Dec 19, 2025, at 10:49, Xuneng Zhou <[email protected]> wrote:
> 
> Hi,
> 
> On Thu, Dec 18, 2025 at 8:25 PM Alexander Korotkov <[email protected]> wrote:
>> 
>> On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
>>> On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
>>>> 
>>>> Hi, Xuneng!
>>>> 
>>>> On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
>>>>> Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
>>>>> statement in v5 patch 1.
>>>> 
>>>> Thank you for your work on this patchset.  Generally, it looks like
>>>> good and quite straightforward extension of the current functionality.
>>>> But this patch adds 4 new unreserved keywords to our grammar.  Do you
>>>> think we can put mode into with options clause?
>>>> 
>>> 
>>> Thanks for pointing this out. Yeah, 4 unreserved keywords add
>>> complexity to the parser and it may not be worthwhile since replay is
>>> expected to be the common use scenario. Maybe we can do something like
>>> this:
>>> 
>>> -- Default (REPLAY mode)
>>> WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
>>> 
>>> -- Explicit REPLAY mode
>>> WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
>>> 
>>> -- WRITE mode
>>> WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
>>> 
>>> If no mode is set explicitly in the options clause, it defaults to
>>> replay. I'll update the patch per your suggestion.
>> 
>> This is exactly what I meant.  Please, go ahead.
>> 
> 
> Here is the updated patch set (v7). Please check.
> 
> -- 
> Best,
> Xuneng
> <v7-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch><v7-0004-Use-WAIT-FOR-LSN-in.patch><v7-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch><v7-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch>

Hi Xuneng,

A solid patch! Just a few small comments:

1 - 0001
```
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
```

As you add pg_unreachable() in the new function GetCurrentLSNForWaitType(), I’m thinking if we should just do an Assert(), I saw every existing related function has done such an assert, for example addLSNWaiter(), it does “Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT);”. I guess we can just following the current mechanism to verify lsnType. So, for GetCurrentLSNForWaitType(), we can just add a default clause and Assert(false).

2 - 0002
```
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
+								"MODE", mode_str),
```

I wonder why don’t we directly put MODE into the error message?

3 - 0002
```
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				if (PromoteIsTriggered())
 				{
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
```

currentLSN is only used in the if clause, thus it can be defined inside the if clause.

3 - 0002
```
+	/*
+	 * If we wrote an LSN that someone was waiting for then walk over the
+	 * shared memory array and set latches to notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
```

Do we need to mention "walk over the shared memory array and set latches” in the comment? The logic belongs to WaitLSNWakeup(). What about if the wake up logic changes in future, then this comment would become stale. So I think we only need to mention “notify the waiters”.


4 - 0003
```
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.  mode takes a string value ('replay', 'write', 'flush'),
+	 * timeout takes a string value, no_throw takes no value.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
```

The new comment has lost the meaning of “We don’t offer completions for these values (timeout and no_throw)”, to be explicit, I feel we can retain the sentence.

5 - 0004
```
+	my $isrecovery =
+	  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+	chomp($isrecovery);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
@@ -3347,9 +3350,6 @@ sub wait_for_catchup
 	}
 	if (!defined($target_lsn))
 	{
-		my $isrecovery =
-		  $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
-		chomp($isrecovery);
```

I wonder why pull up pg_is_in_recovery to an early place and unconditionally call it?

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
@ 2025-12-26 16:15                                                                                               ` Xuneng Zhou <[email protected]>
  2025-12-30 02:12                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-26 16:15 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Chao,

Thanks a lot for your review!

On Fri, Dec 26, 2025 at 4:25 PM Chao Li <[email protected]> wrote:
>
>
>
> > On Dec 19, 2025, at 10:49, Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Thu, Dec 18, 2025 at 8:25 PM Alexander Korotkov <[email protected]> wrote:
> >>
> >> On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> >>> On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
> >>>>
> >>>> Hi, Xuneng!
> >>>>
> >>>> On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> >>>>> Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> >>>>> statement in v5 patch 1.
> >>>>
> >>>> Thank you for your work on this patchset.  Generally, it looks like
> >>>> good and quite straightforward extension of the current functionality.
> >>>> But this patch adds 4 new unreserved keywords to our grammar.  Do you
> >>>> think we can put mode into with options clause?
> >>>>
> >>>
> >>> Thanks for pointing this out. Yeah, 4 unreserved keywords add
> >>> complexity to the parser and it may not be worthwhile since replay is
> >>> expected to be the common use scenario. Maybe we can do something like
> >>> this:
> >>>
> >>> -- Default (REPLAY mode)
> >>> WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
> >>>
> >>> -- Explicit REPLAY mode
> >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
> >>>
> >>> -- WRITE mode
> >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
> >>>
> >>> If no mode is set explicitly in the options clause, it defaults to
> >>> replay. I'll update the patch per your suggestion.
> >>
> >> This is exactly what I meant.  Please, go ahead.
> >>
> >
> > Here is the updated patch set (v7). Please check.
> >
> > --
> > Best,
> > Xuneng
> > <v7-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch><v7-0004-Use-WAIT-FOR-LSN-in.patch><v7-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch><v7-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch>
>
> Hi Xuneng,
>
> A solid patch! Just a few small comments:
>
> 1 - 0001
> ```
> +XLogRecPtr
> +GetCurrentLSNForWaitType(WaitLSNType lsnType)
> +{
> +       switch (lsnType)
> +       {
> +               case WAIT_LSN_TYPE_STANDBY_REPLAY:
> +                       return GetXLogReplayRecPtr(NULL);
> +
> +               case WAIT_LSN_TYPE_STANDBY_WRITE:
> +                       return GetWalRcvWriteRecPtr();
> +
> +               case WAIT_LSN_TYPE_STANDBY_FLUSH:
> +                       return GetWalRcvFlushRecPtr(NULL, NULL);
> +
> +               case WAIT_LSN_TYPE_PRIMARY_FLUSH:
> +                       return GetFlushRecPtr(NULL);
> +       }
> +
> +       elog(ERROR, "invalid LSN wait type: %d", lsnType);
> +       pg_unreachable();
> +}
> ```
>
> As you add pg_unreachable() in the new function GetCurrentLSNForWaitType(), I’m thinking if we should just do an Assert(), I saw every existing related function has done such an assert, for example addLSNWaiter(), it does “Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT);”. I guess we can just following the current mechanism to verify lsnType. So, for GetCurrentLSNForWaitType(), we can just add a default clause and Assert(false).

My take is that Assert(false) alone might not be enough here, since
assertions vanish in non-assert builds. An unexpected lsnType is a
real bug even in production, so keeping a hard error plus
pg_unreachable() seems to be a safer pattern. It also acts as a
guardrail for future extensions — if new wait types are added without
updating this code, we’ll fail loudly rather than silently returning
an incorrect LSN. Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT) was added
to the top of the function.

> 2 - 0002
> ```
> +                       else
> +                               ereport(ERROR,
> +                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                                                errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
> +                                                               "MODE", mode_str),
> ```
>
> I wonder why don’t we directly put MODE into the error message?

Yeah, putting MODE into the error message is cleaner. It's done in v8.

> 3 - 0002
> ```
>                 case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
>                         if (throw)
>                         {
> +                               const           WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
> +                               XLogRecPtr      currentLSN = GetCurrentLSNForWaitType(lsnType);
> +
>                                 if (PromoteIsTriggered())
>                                 {
>                                         ereport(ERROR,
>                                                         errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
>                                                         errmsg("recovery is not in progress"),
> -                                                       errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
> +                                                       errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
>                                                                           LSN_FORMAT_ARGS(lsn),
> -                                                                         LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> +                                                                         desc->verb,
> +                                                                         desc->noun,
> +                                                                         LSN_FORMAT_ARGS(currentLSN)));
>                                 }
>                                 else
>                                         ereport(ERROR,
>                                                         errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
>                                                         errmsg("recovery is not in progress"),
> -                                                       errhint("Waiting for the replay LSN can only be executed during recovery."));
> +                                                       errhint("Waiting for the %s LSN can only be executed during recovery.",
> +                                                                       desc->noun));
>                         }
> ```
>
> currentLSN is only used in the if clause, thus it can be defined inside the if clause.

+ 1.

> 3 - 0002
> ```
> +       /*
> +        * If we wrote an LSN that someone was waiting for then walk over the
> +        * shared memory array and set latches to notify the waiters.
> +        */
> +       if (waitLSNState &&
> +               (LogstreamResult.Write >=
> +                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> +               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> ```
>
> Do we need to mention "walk over the shared memory array and set latches” in the comment? The logic belongs to WaitLSNWakeup(). What about if the wake up logic changes in future, then this comment would become stale. So I think we only need to mention “notify the waiters”.
>

It makes sense to me. They are incorporated into v8.

>
> 4 - 0003
> ```
> +       /*
> +        * Handle parenthesized option list.  This fires when we're in an
> +        * unfinished parenthesized option list.  get_previous_words treats a
> +        * completed parenthesized option list as one word, so the above test is
> +        * correct.  mode takes a string value ('replay', 'write', 'flush'),
> +        * timeout takes a string value, no_throw takes no value.
> +        */
>         else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
>                          !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
>         {
> -               /*
> -                * This fires if we're in an unfinished parenthesized option list.
> -                * get_previous_words treats a completed parenthesized option list as
> -                * one word, so the above test is correct.
> -                */
>                 if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
> -                       COMPLETE_WITH("timeout", "no_throw");
> -
> -               /*
> -                * timeout takes a string value, no_throw takes no value. We don't
> -                * offer completions for these values.
> -                */
> ```
>
> The new comment has lost the meaning of “We don’t offer completions for these values (timeout and no_throw)”, to be explicit, I feel we can retain the sentence.

 The sentence is retained.

> 5 - 0004
> ```
> +       my $isrecovery =
> +         $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
> +       chomp($isrecovery);
>         croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
>           . join(', ', keys(%valid_modes))
>           unless exists($valid_modes{$mode});
> @@ -3347,9 +3350,6 @@ sub wait_for_catchup
>         }
>         if (!defined($target_lsn))
>         {
> -               my $isrecovery =
> -                 $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
> -               chomp($isrecovery);
> ```
>
> I wonder why pull up pg_is_in_recovery to an early place and unconditionally call it?
>

This seems unnecessary. I also realized that my earlier approach in
patch 4 may have been semantically incorrect — it could end up waiting
for the LSN to replay/write/flush on the node itself, rather than on
the downstream standby, which defeats the purpose of
wait_for_catchup(). Patch 4 attempts to address this by running WAIT
FOR LSN on the standby itself.

Support for primary-flush waiting and the refactoring of existing
modes have been also incorporated in v8 following Alexander’s
feedback. The major updates are in patches 2 and 4. Please check.

--
Best,
Xuneng


Attachments:

  [application/octet-stream] v8-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (11.5K, ../../CABPTF7WDzTs2EEB6c5QmsV-svF4rKSTLOS1pWM+7ydUN6tnFOQ@mail.gmail.com/2-v8-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From 668956d0d0794c489167912d54d4c9c7bb237754 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v8 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE option.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 96 +++++++++++++++----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 ++-
 6 files changed, 93 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1b7ef589fc0..fdb92deac57 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6280,7 +6280,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 38b594d2170..2d81bb1a9a7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1856,8 +1856,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..5f4ff50cf38 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,47 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
+
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -302,6 +349,19 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Check if the given LSN type requires recovery to be in progress.
+ * Standby wait types (replay, write, flush) require recovery;
+ * primary wait types (flush) do not.
+ */
+static inline bool
+WaitLSNTypeRequiresRecovery(WaitLSNType t)
+{
+	return t == WAIT_LSN_TYPE_STANDBY_REPLAY ||
+		t == WAIT_LSN_TYPE_STANDBY_WRITE ||
+		t == WAIT_LSN_TYPE_STANDBY_FLUSH;
+}
+
 /*
  * Wait using MyLatch till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
@@ -341,13 +401,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (WaitLSNTypeRequiresRecovery(lsnType) && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +434,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index dcfadbd5aae..e62054585cb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..4cf13f0ccb3 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v8-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch (2.7K, ../../CABPTF7WDzTs2EEB6c5QmsV-svF4rKSTLOS1pWM+7ydUN6tnFOQ@mail.gmail.com/3-v8-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch)
  download | inline diff:
From de1e5024a8bc757ab2b10daa3c63ab67fbfee0a1 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v8 3/4] Add tab completion for WAIT FOR LSN MODE option

Update psql tab completion to support the optional MODE option in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords. The MODE option controls whether
the wait is evaluated from the standby or primary perspective.

When MODE is specified, completion suggests the valid mode values:
standby_replay, standby_write, standby_flush, and primary_flush.
---
 src/bin/psql/tab-complete.in.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..62d87561169 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5355,8 +5355,10 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be: standby_replay | standby_write | standby_flush | primary_flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5369,21 +5371,25 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.
+	 *
+	 * 'mode' takes a string value ('standby_replay', 'standby_write',
+	 * 'standby_flush', 'primary_flush'). 'timeout' takes a string value, and
+	 * 'no_throw' takes no value. We do not offer completions for the *values*
+	 * of 'timeout' or 'no_throw'.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v8-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (4.6K, ../../CABPTF7WDzTs2EEB6c5QmsV-svF4rKSTLOS1pWM+7ydUN6tnFOQ@mail.gmail.com/4-v8-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From e693ce76edb3bd93078dca152e0b551b871db859 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v8 4/4] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 59 +++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..51e5324bff3 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,53 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write' => 'standby_write',
+				'flush' => 'standby_flush',);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+			my $output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+
+			if ($output ne 'success')
+			{
+				# Fetch additional detail for debugging purposes
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN failed with status:
+	${output});
+				diag qq(Last pg_stat_replication contents:
+	${details});
+				croak "failed waiting for catchup";
+			}
+			print "done\n";
+			return;
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v8-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch (42.1K, ../../CABPTF7WDzTs2EEB6c5QmsV-svF4rKSTLOS1pWM+7ydUN6tnFOQ@mail.gmail.com/5-v8-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 73513f3aacddb8e9d8762215a6c2000fc21f1589 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v8 2/4] Add MODE option to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE option in the
WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
- 'standby_replay' (default): Wait for WAL to be replayed to the specified LSN
- 'standby_write': Wait for WAL to be written (received) to the specified LSN
- 'standby_flush': Wait for WAL to be flushed to disk at the specified LSN
- 'primary_flush': Wait for WAL to be flushed to disk on the primary server

The default mode is 'standby_replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands where options are specified as string values in the WITH clause.

Modes are explicitly named to distinguish between primary and standby operations:
- Standby modes ('standby_replay', 'standby_write', 'standby_flush') can only
  be used during recovery (on a standby server)
- Primary mode ('primary_flush') can only be used on a primary server

The 'standby_write' and 'standby_flush' modes are useful for scenarios where
applications need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete. The 'primary_flush' mode
allows waiting for WAL to be flushed on the primary server.

Also includes:
- Documentation updates for the new syntax and mode descriptions
- Test coverage for all four modes including error cases and concurrent waiters
- Wakeup logic in walreceiver for standby write/flush waiters
- Wakeup logic in WAL writer for primary flush waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 213 +++++++++---
 src/backend/access/transam/xlog.c       |  22 +-
 src/backend/commands/wait.c             |  96 +++++-
 src/backend/replication/walreceiver.c   |  18 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 411 ++++++++++++++++++++++--
 5 files changed, 673 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..df72b3327c8 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    standby_replay | standby_write | standby_flush | primary_flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,27 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+  </para>
+
+  <para>
+   On timeout, an error is emitted unless <literal>NO_THROW</literal>
+   is specified in the WITH clause. For standby modes
+   (<literal>standby_replay</literal>, <literal>standby_write</literal>,
+   <literal>standby_flush</literal>), an error is also emitted if the
+   server is promoted before the <parameter>lsn</parameter> is reached.
+   If <parameter>NO_THROW</parameter> is specified, the command returns
+   a status string instead of throwing errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +85,65 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>standby_replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>standby_replay</literal>: Wait for the LSN to be replayed
+            (applied to the database) on a standby server. After successful
+            completion, <function>pg_last_wal_replay_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_write</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and written to disk on a
+            standby server, but not yet flushed. This is faster than
+            <literal>standby_flush</literal> but provides weaker durability
+            guarantees since the data may still be in operating system
+            buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN. This mode can
+            only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_flush</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and flushed to disk on a
+            standby server. This provides a durability guarantee without
+            waiting for the WAL to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>primary_flush</literal>: Wait for the WAL containing the
+            LSN to be flushed to disk on a primary server. After successful
+            completion, <function>pg_current_wal_flush_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used on a primary server (not during recovery).
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +207,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +223,34 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>standby_replay</literal> mode
+   waits for the LSN to be replayed (applied to the database), which is
+   useful to achieve read-your-writes consistency while using an async
+   replica for reads and the primary for writes. The
+   <literal>standby_flush</literal> mode waits for the WAL to be flushed
+   to durable storage on the replica, providing a durability guarantee
+   without waiting for replay. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, which is
+   faster than flush but provides weaker durability guarantees. The
+   <literal>primary_flush</literal> mode waits for WAL to be flushed on
+   a primary server. In all cases, the <acronym>LSN</acronym> of the last
+   modification should be stored on the client application side or the
+   connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   The standby modes (<literal>standby_replay</literal>,
+   <literal>standby_write</literal>, <literal>standby_flush</literal>)
+   can only be used during recovery, and <literal>primary_flush</literal>
+   can only be used on a primary server. Using the wrong mode for the
+   current server state will result in an error. If a standby is promoted
+   while waiting with a standby mode, the command will return
+   <literal>not in recovery</literal> (or throw an error if
+   <literal>NO_THROW</literal> is not specified). Promotion creates a new
+   timeline, and the LSN being waited for may refer to WAL from the old
+   timeline.
   </para>
 
 </refsect1>
@@ -175,21 +259,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +284,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +295,43 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for flush on primary:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'primary_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +341,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdb92deac57..da96b627228 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2918,6 +2918,14 @@ XLogFlush(XLogRecPtr record)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * If we still haven't flushed to the request point then we have a
 	 * problem; most likely, the requested flush point is past end of XLOG.
@@ -3100,6 +3108,14 @@ XLogBackgroundFlush(void)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
@@ -6277,10 +6293,12 @@ StartupXLOG(void)
 	WakeupCheckpointer();
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..a85c3b0de98 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,18 +29,39 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* Mode name: "standby_replay",
+								 * "standby_write", "standby_flush",
+								 * "primary_flush" */
+	const char *verb;			/* Past participle: "replayed", "written",
+								 * "flushed" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"standby_replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"standby_write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"standby_flush", "flushed"},
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = {"primary_flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +69,32 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "standby_replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "standby_write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "standby_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else if (pg_strcasecmp(mode_str, "primary_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_PRIMARY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for WAIT option \"MODE\": \"%s\"",
+								mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +154,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +187,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	/*
+	 * Validate that the requested mode matches the current server state.
+	 * Primary modes can only be used on a primary.
+	 */
+	if (lsnType == WAIT_LSN_TYPE_PRIMARY_FLUSH)
+	{
+		if (RecoveryInProgress())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("recovery is in progress"),
+					 errhint("Waiting for primary_flush can only be done on a primary server. "
+							 "Use standby_flush mode on a standby server.")));
+	}
+
+	/* Now wait for the LSN */
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +216,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +235,27 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+
 				if (PromoteIsTriggered())
 				{
+					XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..404d348da37 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,14 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1013,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for, notify the
+		 * waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..e41aad45e28 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,5 +1,6 @@
-# Checks waiting for the LSN replay on standby using
-# the WAIT FOR command.
+# Checks waiting for the LSN using the WAIT FOR command.
+# Tests standby modes (standby_replay/standby_write/standby_flush) on standby
+# and primary_flush mode on primary.
 use strict;
 use warnings FATAL => 'all';
 
@@ -7,6 +8,42 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql(
+		'postgres', qq[
+		SELECT pg_catalog.quote_literal(setting)
+		FROM pg_settings
+		WHERE name = 'primary_conninfo';
+	]);
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = $saved_primary_conninfo;
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +99,52 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with standby_write, standby_flush, and
+# primary_flush modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'standby_write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'standby_write'"
+);
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'standby_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'standby_flush'"
+);
+
+# Check primary_flush mode on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(51, 60))");
+my $lsn_primary_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_primary->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_primary_flush}' WITH (MODE 'primary_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '${lsn_primary_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"primary flushed WAL up to target LSN after WAIT FOR with MODE 'primary_flush'"
+);
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,14 +170,26 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
-# within another function, or inside a transaction with an isolation level
-# higher than READ COMMITTED.
+# 5. Check mode validation: standby modes error on primary, primary mode errors
+# on standby, and primary_flush works on primary.  Also check that WAIT FOR
+# triggers an error if called within another function or inside a transaction
+# with an isolation level higher than READ COMMITTED.
+
+# Test standby_flush on primary - should error
+$node_primary->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'standby_flush');",
+	stderr => \$stderr);
+ok($stderr =~ /recovery is not in progress/,
+	"get an error when running standby_flush on the primary");
 
-$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+# Test primary_flush on standby - should error
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'primary_flush');",
 	stderr => \$stderr);
-ok( $stderr =~ /recovery is not in progress/,
-	"get an error when running on the primary");
+ok($stderr =~ /recovery is in progress/,
+	"get an error when running primary_flush on the standby");
 
 $node_standby->psql(
 	'postgres',
@@ -125,7 +219,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,10 +302,26 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
-# psql sessions each waiting for a corresponding insertion.  When waiting is
-# finished, stored procedures logs if there are visible as many rows as
-# should be.
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "MODE": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'standby_replay', MODE 'standby_write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple standby_replay waiters.  We make 5
+# background psql sessions each waiting for a corresponding insertion.  When
+# waiting is finished, stored procedures logs if there are visible as many
+# rows as should be.
 $node_primary->safe_psql(
 	'postgres', qq[
 CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
@@ -225,8 +335,17 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
   END
 \$\$
 LANGUAGE plpgsql;
+
+CREATE FUNCTION log_wait_done(prefix text, i int) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '% %', prefix, i;
+  END
+\$\$
+LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +362,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +371,246 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple standby_replay waiters reported consistent data');
+
+# 7b. Check the scenario of multiple standby_write waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_write waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d');
+		SELECT log_wait_done('write_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_write waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple standby_flush waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_flush waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d');
+		SELECT log_wait_done('flush_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_flush waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed standby mode waiters (standby_replay,
+# standby_write, standby_flush) running concurrently.  We start 6 sessions:
+# 2 for each mode, all waiting for the same target LSN.  We stop the
+# walreceiver and pause replay to ensure all waiters block.  Then we resume
+# replay and restart the walreceiver to verify they unblock and complete
+# correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		SELECT log_wait_done('mixed_done', $i);
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 7e. Check the scenario of multiple primary_flush waiters on primary.
+# We start 5 background sessions waiting for different LSNs with primary_flush
+# mode.  Each waiter logs when done.
+my @primary_flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (400 + ${i});");
+	$primary_flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+my $primary_flush_log_offset = -s $node_primary->logfile;
+
+# Start primary_flush waiters
+my @primary_flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$primary_flush_sessions[$i] = $node_primary->background_psql('postgres');
+	$primary_flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d');
+		SELECT log_wait_done('primary_flush_done', $i);
+	]);
+}
+
+# The WAL should already be flushed, so waiters should complete quickly
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->wait_for_log("primary_flush_done $i",
+		$primary_flush_log_offset);
+	$primary_flush_sessions[$i]->quit;
+}
+
+# Verify on primary that WAL was flushed up to the target LSN
+$output = $node_primary->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '$primary_flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple primary_flush waiters: primary flushed WAL up to target LSN");
+
+# 8. Check that the standby promotion terminates all standby wait modes.  Start
+# waiting for unreachable LSNs with standby_replay, standby_write, and
+# standby_flush modes, then promote.  Check the log for the relevant error
+# messages.  Also, check that waiting for already replayed LSN doesn't cause
+# an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +620,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +645,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-30 02:12                                                                                                 ` Xuneng Zhou <[email protected]>
  2025-12-30 02:42                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-30 02:12 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Sat, Dec 27, 2025 at 12:15 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi Chao,
>
> Thanks a lot for your review!
>
> On Fri, Dec 26, 2025 at 4:25 PM Chao Li <[email protected]> wrote:
> >
> >
> >
> > > On Dec 19, 2025, at 10:49, Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Thu, Dec 18, 2025 at 8:25 PM Alexander Korotkov <[email protected]> wrote:
> > >>
> > >> On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> > >>> On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
> > >>>>
> > >>>> Hi, Xuneng!
> > >>>>
> > >>>> On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> > >>>>> Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> > >>>>> statement in v5 patch 1.
> > >>>>
> > >>>> Thank you for your work on this patchset.  Generally, it looks like
> > >>>> good and quite straightforward extension of the current functionality.
> > >>>> But this patch adds 4 new unreserved keywords to our grammar.  Do you
> > >>>> think we can put mode into with options clause?
> > >>>>
> > >>>
> > >>> Thanks for pointing this out. Yeah, 4 unreserved keywords add
> > >>> complexity to the parser and it may not be worthwhile since replay is
> > >>> expected to be the common use scenario. Maybe we can do something like
> > >>> this:
> > >>>
> > >>> -- Default (REPLAY mode)
> > >>> WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
> > >>>
> > >>> -- Explicit REPLAY mode
> > >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
> > >>>
> > >>> -- WRITE mode
> > >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
> > >>>
> > >>> If no mode is set explicitly in the options clause, it defaults to
> > >>> replay. I'll update the patch per your suggestion.
> > >>
> > >> This is exactly what I meant.  Please, go ahead.
> > >>
> > >
> > > Here is the updated patch set (v7). Please check.
> > >
> > > --
> > > Best,
> > > Xuneng
> > > <v7-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch><v7-0004-Use-WAIT-FOR-LSN-in.patch><v7-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch><v7-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch>
> >
> > Hi Xuneng,
> >
> > A solid patch! Just a few small comments:
> >
> > 1 - 0001
> > ```
> > +XLogRecPtr
> > +GetCurrentLSNForWaitType(WaitLSNType lsnType)
> > +{
> > +       switch (lsnType)
> > +       {
> > +               case WAIT_LSN_TYPE_STANDBY_REPLAY:
> > +                       return GetXLogReplayRecPtr(NULL);
> > +
> > +               case WAIT_LSN_TYPE_STANDBY_WRITE:
> > +                       return GetWalRcvWriteRecPtr();
> > +
> > +               case WAIT_LSN_TYPE_STANDBY_FLUSH:
> > +                       return GetWalRcvFlushRecPtr(NULL, NULL);
> > +
> > +               case WAIT_LSN_TYPE_PRIMARY_FLUSH:
> > +                       return GetFlushRecPtr(NULL);
> > +       }
> > +
> > +       elog(ERROR, "invalid LSN wait type: %d", lsnType);
> > +       pg_unreachable();
> > +}
> > ```
> >
> > As you add pg_unreachable() in the new function GetCurrentLSNForWaitType(), I’m thinking if we should just do an Assert(), I saw every existing related function has done such an assert, for example addLSNWaiter(), it does “Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT);”. I guess we can just following the current mechanism to verify lsnType. So, for GetCurrentLSNForWaitType(), we can just add a default clause and Assert(false).
>
> My take is that Assert(false) alone might not be enough here, since
> assertions vanish in non-assert builds. An unexpected lsnType is a
> real bug even in production, so keeping a hard error plus
> pg_unreachable() seems to be a safer pattern. It also acts as a
> guardrail for future extensions — if new wait types are added without
> updating this code, we’ll fail loudly rather than silently returning
> an incorrect LSN. Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT) was added
> to the top of the function.
>
> > 2 - 0002
> > ```
> > +                       else
> > +                               ereport(ERROR,
> > +                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> > +                                                errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
> > +                                                               "MODE", mode_str),
> > ```
> >
> > I wonder why don’t we directly put MODE into the error message?
>
> Yeah, putting MODE into the error message is cleaner. It's done in v8.
>
> > 3 - 0002
> > ```
> >                 case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
> >                         if (throw)
> >                         {
> > +                               const           WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
> > +                               XLogRecPtr      currentLSN = GetCurrentLSNForWaitType(lsnType);
> > +
> >                                 if (PromoteIsTriggered())
> >                                 {
> >                                         ereport(ERROR,
> >                                                         errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> >                                                         errmsg("recovery is not in progress"),
> > -                                                       errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
> > +                                                       errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
> >                                                                           LSN_FORMAT_ARGS(lsn),
> > -                                                                         LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> > +                                                                         desc->verb,
> > +                                                                         desc->noun,
> > +                                                                         LSN_FORMAT_ARGS(currentLSN)));
> >                                 }
> >                                 else
> >                                         ereport(ERROR,
> >                                                         errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> >                                                         errmsg("recovery is not in progress"),
> > -                                                       errhint("Waiting for the replay LSN can only be executed during recovery."));
> > +                                                       errhint("Waiting for the %s LSN can only be executed during recovery.",
> > +                                                                       desc->noun));
> >                         }
> > ```
> >
> > currentLSN is only used in the if clause, thus it can be defined inside the if clause.
>
> + 1.
>
> > 3 - 0002
> > ```
> > +       /*
> > +        * If we wrote an LSN that someone was waiting for then walk over the
> > +        * shared memory array and set latches to notify the waiters.
> > +        */
> > +       if (waitLSNState &&
> > +               (LogstreamResult.Write >=
> > +                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> > +               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> > ```
> >
> > Do we need to mention "walk over the shared memory array and set latches” in the comment? The logic belongs to WaitLSNWakeup(). What about if the wake up logic changes in future, then this comment would become stale. So I think we only need to mention “notify the waiters”.
> >
>
> It makes sense to me. They are incorporated into v8.
>
> >
> > 4 - 0003
> > ```
> > +       /*
> > +        * Handle parenthesized option list.  This fires when we're in an
> > +        * unfinished parenthesized option list.  get_previous_words treats a
> > +        * completed parenthesized option list as one word, so the above test is
> > +        * correct.  mode takes a string value ('replay', 'write', 'flush'),
> > +        * timeout takes a string value, no_throw takes no value.
> > +        */
> >         else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
> >                          !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
> >         {
> > -               /*
> > -                * This fires if we're in an unfinished parenthesized option list.
> > -                * get_previous_words treats a completed parenthesized option list as
> > -                * one word, so the above test is correct.
> > -                */
> >                 if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
> > -                       COMPLETE_WITH("timeout", "no_throw");
> > -
> > -               /*
> > -                * timeout takes a string value, no_throw takes no value. We don't
> > -                * offer completions for these values.
> > -                */
> > ```
> >
> > The new comment has lost the meaning of “We don’t offer completions for these values (timeout and no_throw)”, to be explicit, I feel we can retain the sentence.
>
>  The sentence is retained.
>
> > 5 - 0004
> > ```
> > +       my $isrecovery =
> > +         $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
> > +       chomp($isrecovery);
> >         croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
> >           . join(', ', keys(%valid_modes))
> >           unless exists($valid_modes{$mode});
> > @@ -3347,9 +3350,6 @@ sub wait_for_catchup
> >         }
> >         if (!defined($target_lsn))
> >         {
> > -               my $isrecovery =
> > -                 $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
> > -               chomp($isrecovery);
> > ```
> >
> > I wonder why pull up pg_is_in_recovery to an early place and unconditionally call it?
> >
>
> This seems unnecessary. I also realized that my earlier approach in
> patch 4 may have been semantically incorrect — it could end up waiting
> for the LSN to replay/write/flush on the node itself, rather than on
> the downstream standby, which defeats the purpose of
> wait_for_catchup(). Patch 4 attempts to address this by running WAIT
> FOR LSN on the standby itself.
>
> Support for primary-flush waiting and the refactoring of existing
> modes have been also incorporated in v8 following Alexander’s
> feedback. The major updates are in patches 2 and 4. Please check.
>

Added WaitLSNTypeDesc to typedefs.list in v9 patch 2.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v9-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch (11.5K, ../../CABPTF7VT=4+GegrqFC9DUSnv3LSchx8uYZSi8gFm372z3dC=qA@mail.gmail.com/2-v9-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch)
  download | inline diff:
From 668956d0d0794c489167912d54d4c9c7bb237754 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v9 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 96 +++++++++++++++----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 ++-
 6 files changed, 93 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1b7ef589fc0..fdb92deac57 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6280,7 +6280,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 38b594d2170..2d81bb1a9a7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1856,8 +1856,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..5f4ff50cf38 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,47 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
+
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -302,6 +349,19 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Check if the given LSN type requires recovery to be in progress.
+ * Standby wait types (replay, write, flush) require recovery;
+ * primary wait types (flush) do not.
+ */
+static inline bool
+WaitLSNTypeRequiresRecovery(WaitLSNType t)
+{
+	return t == WAIT_LSN_TYPE_STANDBY_REPLAY ||
+		t == WAIT_LSN_TYPE_STANDBY_WRITE ||
+		t == WAIT_LSN_TYPE_STANDBY_FLUSH;
+}
+
 /*
  * Wait using MyLatch till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
@@ -341,13 +401,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (WaitLSNTypeRequiresRecovery(lsnType) && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +434,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index dcfadbd5aae..e62054585cb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..4cf13f0ccb3 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v9-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch (2.7K, ../../CABPTF7VT=4+GegrqFC9DUSnv3LSchx8uYZSi8gFm372z3dC=qA@mail.gmail.com/3-v9-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch)
  download | inline diff:
From dd0d980c031271eeada1c6f8dfc40ff7d58ced09 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v9 3/4] Add tab completion for WAIT FOR LSN MODE option

Update psql tab completion to support the optional MODE option in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords. The MODE option controls whether
the wait is evaluated from the standby or primary perspective.

When MODE is specified, completion suggests the valid mode values:
standby_replay, standby_write, standby_flush, and primary_flush.
---
 src/bin/psql/tab-complete.in.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..62d87561169 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5355,8 +5355,10 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be: standby_replay | standby_write | standby_flush | primary_flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5369,21 +5371,25 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.
+	 *
+	 * 'mode' takes a string value ('standby_replay', 'standby_write',
+	 * 'standby_flush', 'primary_flush'). 'timeout' takes a string value, and
+	 * 'no_throw' takes no value. We do not offer completions for the *values*
+	 * of 'timeout' or 'no_throw'.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v9-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch (42.5K, ../../CABPTF7VT=4+GegrqFC9DUSnv3LSchx8uYZSi8gFm372z3dC=qA@mail.gmail.com/4-v9-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From aeb6a6a80e04cd591979181f23afb3584ef85f4d Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v9 2/4] Add MODE option to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE option in the
WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
- 'standby_replay' (default): Wait for WAL to be replayed to the specified LSN
- 'standby_write': Wait for WAL to be written (received) to the specified LSN
- 'standby_flush': Wait for WAL to be flushed to disk at the specified LSN
- 'primary_flush': Wait for WAL to be flushed to disk on the primary server

The default mode is 'standby_replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands where options are specified as string values in the WITH clause.

Modes are explicitly named to distinguish between primary and standby operations:
- Standby modes ('standby_replay', 'standby_write', 'standby_flush') can only
  be used during recovery (on a standby server)
- Primary mode ('primary_flush') can only be used on a primary server

The 'standby_write' and 'standby_flush' modes are useful for scenarios where
applications need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete. The 'primary_flush' mode
allows waiting for WAL to be flushed on the primary server.

Also includes:
- Documentation updates for the new syntax and mode descriptions
- Test coverage for all four modes including error cases and concurrent waiters
- Wakeup logic in walreceiver for standby write/flush waiters
- Wakeup logic in WAL writer for primary flush waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 213 +++++++++---
 src/backend/access/transam/xlog.c       |  22 +-
 src/backend/commands/wait.c             |  96 +++++-
 src/backend/replication/walreceiver.c   |  18 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 411 ++++++++++++++++++++++--
 src/tools/pgindent/typedefs.list        |   1 +
 6 files changed, 674 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..df72b3327c8 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    standby_replay | standby_write | standby_flush | primary_flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,27 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+  </para>
+
+  <para>
+   On timeout, an error is emitted unless <literal>NO_THROW</literal>
+   is specified in the WITH clause. For standby modes
+   (<literal>standby_replay</literal>, <literal>standby_write</literal>,
+   <literal>standby_flush</literal>), an error is also emitted if the
+   server is promoted before the <parameter>lsn</parameter> is reached.
+   If <parameter>NO_THROW</parameter> is specified, the command returns
+   a status string instead of throwing errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +85,65 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>standby_replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>standby_replay</literal>: Wait for the LSN to be replayed
+            (applied to the database) on a standby server. After successful
+            completion, <function>pg_last_wal_replay_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_write</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and written to disk on a
+            standby server, but not yet flushed. This is faster than
+            <literal>standby_flush</literal> but provides weaker durability
+            guarantees since the data may still be in operating system
+            buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN. This mode can
+            only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_flush</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and flushed to disk on a
+            standby server. This provides a durability guarantee without
+            waiting for the WAL to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>primary_flush</literal>: Wait for the WAL containing the
+            LSN to be flushed to disk on a primary server. After successful
+            completion, <function>pg_current_wal_flush_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used on a primary server (not during recovery).
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +207,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +223,34 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>standby_replay</literal> mode
+   waits for the LSN to be replayed (applied to the database), which is
+   useful to achieve read-your-writes consistency while using an async
+   replica for reads and the primary for writes. The
+   <literal>standby_flush</literal> mode waits for the WAL to be flushed
+   to durable storage on the replica, providing a durability guarantee
+   without waiting for replay. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, which is
+   faster than flush but provides weaker durability guarantees. The
+   <literal>primary_flush</literal> mode waits for WAL to be flushed on
+   a primary server. In all cases, the <acronym>LSN</acronym> of the last
+   modification should be stored on the client application side or the
+   connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   The standby modes (<literal>standby_replay</literal>,
+   <literal>standby_write</literal>, <literal>standby_flush</literal>)
+   can only be used during recovery, and <literal>primary_flush</literal>
+   can only be used on a primary server. Using the wrong mode for the
+   current server state will result in an error. If a standby is promoted
+   while waiting with a standby mode, the command will return
+   <literal>not in recovery</literal> (or throw an error if
+   <literal>NO_THROW</literal> is not specified). Promotion creates a new
+   timeline, and the LSN being waited for may refer to WAL from the old
+   timeline.
   </para>
 
 </refsect1>
@@ -175,21 +259,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +284,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +295,43 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for flush on primary:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'primary_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +341,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdb92deac57..da96b627228 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2918,6 +2918,14 @@ XLogFlush(XLogRecPtr record)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * If we still haven't flushed to the request point then we have a
 	 * problem; most likely, the requested flush point is past end of XLOG.
@@ -3100,6 +3108,14 @@ XLogBackgroundFlush(void)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
@@ -6277,10 +6293,12 @@ StartupXLOG(void)
 	WakeupCheckpointer();
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..a85c3b0de98 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,18 +29,39 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* Mode name: "standby_replay",
+								 * "standby_write", "standby_flush",
+								 * "primary_flush" */
+	const char *verb;			/* Past participle: "replayed", "written",
+								 * "flushed" */
+}			WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"standby_replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"standby_write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"standby_flush", "flushed"},
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = {"primary_flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +69,32 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "standby_replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "standby_write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "standby_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else if (pg_strcasecmp(mode_str, "primary_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_PRIMARY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for WAIT option \"MODE\": \"%s\"",
+								mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +154,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +187,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	/*
+	 * Validate that the requested mode matches the current server state.
+	 * Primary modes can only be used on a primary.
+	 */
+	if (lsnType == WAIT_LSN_TYPE_PRIMARY_FLUSH)
+	{
+		if (RecoveryInProgress())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("recovery is in progress"),
+					 errhint("Waiting for primary_flush can only be done on a primary server. "
+							 "Use standby_flush mode on a standby server.")));
+	}
+
+	/* Now wait for the LSN */
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +216,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +235,27 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const		WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+
 				if (PromoteIsTriggered())
 				{
+					XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..404d348da37 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,14 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1013,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for, notify the
+		 * waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..e41aad45e28 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,5 +1,6 @@
-# Checks waiting for the LSN replay on standby using
-# the WAIT FOR command.
+# Checks waiting for the LSN using the WAIT FOR command.
+# Tests standby modes (standby_replay/standby_write/standby_flush) on standby
+# and primary_flush mode on primary.
 use strict;
 use warnings FATAL => 'all';
 
@@ -7,6 +8,42 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql(
+		'postgres', qq[
+		SELECT pg_catalog.quote_literal(setting)
+		FROM pg_settings
+		WHERE name = 'primary_conninfo';
+	]);
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = $saved_primary_conninfo;
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +99,52 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with standby_write, standby_flush, and
+# primary_flush modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'standby_write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'standby_write'"
+);
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'standby_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'standby_flush'"
+);
+
+# Check primary_flush mode on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(51, 60))");
+my $lsn_primary_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_primary->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_primary_flush}' WITH (MODE 'primary_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '${lsn_primary_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"primary flushed WAL up to target LSN after WAIT FOR with MODE 'primary_flush'"
+);
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,14 +170,26 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
-# within another function, or inside a transaction with an isolation level
-# higher than READ COMMITTED.
+# 5. Check mode validation: standby modes error on primary, primary mode errors
+# on standby, and primary_flush works on primary.  Also check that WAIT FOR
+# triggers an error if called within another function or inside a transaction
+# with an isolation level higher than READ COMMITTED.
+
+# Test standby_flush on primary - should error
+$node_primary->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'standby_flush');",
+	stderr => \$stderr);
+ok($stderr =~ /recovery is not in progress/,
+	"get an error when running standby_flush on the primary");
 
-$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+# Test primary_flush on standby - should error
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'primary_flush');",
 	stderr => \$stderr);
-ok( $stderr =~ /recovery is not in progress/,
-	"get an error when running on the primary");
+ok($stderr =~ /recovery is in progress/,
+	"get an error when running primary_flush on the standby");
 
 $node_standby->psql(
 	'postgres',
@@ -125,7 +219,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,10 +302,26 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
-# psql sessions each waiting for a corresponding insertion.  When waiting is
-# finished, stored procedures logs if there are visible as many rows as
-# should be.
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "MODE": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'standby_replay', MODE 'standby_write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple standby_replay waiters.  We make 5
+# background psql sessions each waiting for a corresponding insertion.  When
+# waiting is finished, stored procedures logs if there are visible as many
+# rows as should be.
 $node_primary->safe_psql(
 	'postgres', qq[
 CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
@@ -225,8 +335,17 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
   END
 \$\$
 LANGUAGE plpgsql;
+
+CREATE FUNCTION log_wait_done(prefix text, i int) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '% %', prefix, i;
+  END
+\$\$
+LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +362,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +371,246 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple standby_replay waiters reported consistent data');
+
+# 7b. Check the scenario of multiple standby_write waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_write waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d');
+		SELECT log_wait_done('write_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_write waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple standby_flush waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_flush waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d');
+		SELECT log_wait_done('flush_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_flush waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed standby mode waiters (standby_replay,
+# standby_write, standby_flush) running concurrently.  We start 6 sessions:
+# 2 for each mode, all waiting for the same target LSN.  We stop the
+# walreceiver and pause replay to ensure all waiters block.  Then we resume
+# replay and restart the walreceiver to verify they unblock and complete
+# correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		SELECT log_wait_done('mixed_done', $i);
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 7e. Check the scenario of multiple primary_flush waiters on primary.
+# We start 5 background sessions waiting for different LSNs with primary_flush
+# mode.  Each waiter logs when done.
+my @primary_flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (400 + ${i});");
+	$primary_flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+my $primary_flush_log_offset = -s $node_primary->logfile;
+
+# Start primary_flush waiters
+my @primary_flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$primary_flush_sessions[$i] = $node_primary->background_psql('postgres');
+	$primary_flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d');
+		SELECT log_wait_done('primary_flush_done', $i);
+	]);
+}
+
+# The WAL should already be flushed, so waiters should complete quickly
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->wait_for_log("primary_flush_done $i",
+		$primary_flush_log_offset);
+	$primary_flush_sessions[$i]->quit;
+}
+
+# Verify on primary that WAL was flushed up to the target LSN
+$output = $node_primary->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '$primary_flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple primary_flush waiters: primary flushed WAL up to target LSN");
+
+# 8. Check that the standby promotion terminates all standby wait modes.  Start
+# waiting for unreachable LSNs with standby_replay, standby_write, and
+# standby_flush modes, then promote.  Check the log for the relevant error
+# messages.  Also, check that waiting for already replayed LSN doesn't cause
+# an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +620,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +645,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5c88fa92f4e..ab7149c5e62 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3305,6 +3305,7 @@ WaitLSNProcInfo
 WaitLSNResult
 WaitLSNState
 WaitLSNType
+WaitLSNTypeDesc
 WaitPMResult
 WaitStmt
 WalCloseMethod
-- 
2.51.0



  [application/octet-stream] v9-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (4.6K, ../../CABPTF7VT=4+GegrqFC9DUSnv3LSchx8uYZSi8gFm372z3dC=qA@mail.gmail.com/5-v9-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From c35ad3714611db108f1726870d04e6f8b39edc77 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v9 4/4] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 59 +++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..51e5324bff3 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,53 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write' => 'standby_write',
+				'flush' => 'standby_flush',);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+			my $output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+
+			if ($output ne 'success')
+			{
+				# Fetch additional detail for debugging purposes
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN failed with status:
+	${output});
+				diag qq(Last pg_stat_replication contents:
+	${details});
+				croak "failed waiting for catchup";
+			}
+			print "done\n";
+			return;
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 02:12                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-30 02:42                                                                                                   ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-30 02:42 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Dec 30, 2025 at 10:12 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Sat, Dec 27, 2025 at 12:15 AM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi Chao,
> >
> > Thanks a lot for your review!
> >
> > On Fri, Dec 26, 2025 at 4:25 PM Chao Li <[email protected]> wrote:
> > >
> > >
> > >
> > > > On Dec 19, 2025, at 10:49, Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi,
> > > >
> > > > On Thu, Dec 18, 2025 at 8:25 PM Alexander Korotkov <[email protected]> wrote:
> > > >>
> > > >> On Thu, Dec 18, 2025 at 2:24 PM Xuneng Zhou <[email protected]> wrote:
> > > >>> On Thu, Dec 18, 2025 at 6:38 PM Alexander Korotkov <[email protected]> wrote:
> > > >>>>
> > > >>>> Hi, Xuneng!
> > > >>>>
> > > >>>> On Tue, Dec 16, 2025 at 6:46 AM Xuneng Zhou <[email protected]> wrote:
> > > >>>>> Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch
> > > >>>>> statement in v5 patch 1.
> > > >>>>
> > > >>>> Thank you for your work on this patchset.  Generally, it looks like
> > > >>>> good and quite straightforward extension of the current functionality.
> > > >>>> But this patch adds 4 new unreserved keywords to our grammar.  Do you
> > > >>>> think we can put mode into with options clause?
> > > >>>>
> > > >>>
> > > >>> Thanks for pointing this out. Yeah, 4 unreserved keywords add
> > > >>> complexity to the parser and it may not be worthwhile since replay is
> > > >>> expected to be the common use scenario. Maybe we can do something like
> > > >>> this:
> > > >>>
> > > >>> -- Default (REPLAY mode)
> > > >>> WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s');
> > > >>>
> > > >>> -- Explicit REPLAY mode
> > > >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s');
> > > >>>
> > > >>> -- WRITE mode
> > > >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s');
> > > >>>
> > > >>> If no mode is set explicitly in the options clause, it defaults to
> > > >>> replay. I'll update the patch per your suggestion.
> > > >>
> > > >> This is exactly what I meant.  Please, go ahead.
> > > >>
> > > >
> > > > Here is the updated patch set (v7). Please check.
> > > >
> > > > --
> > > > Best,
> > > > Xuneng
> > > > <v7-0001-Extend-xlogwait-infrastructure-with-write-and-flu.patch><v7-0004-Use-WAIT-FOR-LSN-in.patch><v7-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch><v7-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch>
> > >
> > > Hi Xuneng,
> > >
> > > A solid patch! Just a few small comments:
> > >
> > > 1 - 0001
> > > ```
> > > +XLogRecPtr
> > > +GetCurrentLSNForWaitType(WaitLSNType lsnType)
> > > +{
> > > +       switch (lsnType)
> > > +       {
> > > +               case WAIT_LSN_TYPE_STANDBY_REPLAY:
> > > +                       return GetXLogReplayRecPtr(NULL);
> > > +
> > > +               case WAIT_LSN_TYPE_STANDBY_WRITE:
> > > +                       return GetWalRcvWriteRecPtr();
> > > +
> > > +               case WAIT_LSN_TYPE_STANDBY_FLUSH:
> > > +                       return GetWalRcvFlushRecPtr(NULL, NULL);
> > > +
> > > +               case WAIT_LSN_TYPE_PRIMARY_FLUSH:
> > > +                       return GetFlushRecPtr(NULL);
> > > +       }
> > > +
> > > +       elog(ERROR, "invalid LSN wait type: %d", lsnType);
> > > +       pg_unreachable();
> > > +}
> > > ```
> > >
> > > As you add pg_unreachable() in the new function GetCurrentLSNForWaitType(), I’m thinking if we should just do an Assert(), I saw every existing related function has done such an assert, for example addLSNWaiter(), it does “Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT);”. I guess we can just following the current mechanism to verify lsnType. So, for GetCurrentLSNForWaitType(), we can just add a default clause and Assert(false).
> >
> > My take is that Assert(false) alone might not be enough here, since
> > assertions vanish in non-assert builds. An unexpected lsnType is a
> > real bug even in production, so keeping a hard error plus
> > pg_unreachable() seems to be a safer pattern. It also acts as a
> > guardrail for future extensions — if new wait types are added without
> > updating this code, we’ll fail loudly rather than silently returning
> > an incorrect LSN. Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT) was added
> > to the top of the function.
> >
> > > 2 - 0002
> > > ```
> > > +                       else
> > > +                               ereport(ERROR,
> > > +                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> > > +                                                errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
> > > +                                                               "MODE", mode_str),
> > > ```
> > >
> > > I wonder why don’t we directly put MODE into the error message?
> >
> > Yeah, putting MODE into the error message is cleaner. It's done in v8.
> >
> > > 3 - 0002
> > > ```
> > >                 case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
> > >                         if (throw)
> > >                         {
> > > +                               const           WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
> > > +                               XLogRecPtr      currentLSN = GetCurrentLSNForWaitType(lsnType);
> > > +
> > >                                 if (PromoteIsTriggered())
> > >                                 {
> > >                                         ereport(ERROR,
> > >                                                         errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> > >                                                         errmsg("recovery is not in progress"),
> > > -                                                       errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
> > > +                                                       errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
> > >                                                                           LSN_FORMAT_ARGS(lsn),
> > > -                                                                         LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> > > +                                                                         desc->verb,
> > > +                                                                         desc->noun,
> > > +                                                                         LSN_FORMAT_ARGS(currentLSN)));
> > >                                 }
> > >                                 else
> > >                                         ereport(ERROR,
> > >                                                         errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> > >                                                         errmsg("recovery is not in progress"),
> > > -                                                       errhint("Waiting for the replay LSN can only be executed during recovery."));
> > > +                                                       errhint("Waiting for the %s LSN can only be executed during recovery.",
> > > +                                                                       desc->noun));
> > >                         }
> > > ```
> > >
> > > currentLSN is only used in the if clause, thus it can be defined inside the if clause.
> >
> > + 1.
> >
> > > 3 - 0002
> > > ```
> > > +       /*
> > > +        * If we wrote an LSN that someone was waiting for then walk over the
> > > +        * shared memory array and set latches to notify the waiters.
> > > +        */
> > > +       if (waitLSNState &&
> > > +               (LogstreamResult.Write >=
> > > +                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> > > +               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> > > ```
> > >
> > > Do we need to mention "walk over the shared memory array and set latches” in the comment? The logic belongs to WaitLSNWakeup(). What about if the wake up logic changes in future, then this comment would become stale. So I think we only need to mention “notify the waiters”.
> > >
> >
> > It makes sense to me. They are incorporated into v8.
> >
> > >
> > > 4 - 0003
> > > ```
> > > +       /*
> > > +        * Handle parenthesized option list.  This fires when we're in an
> > > +        * unfinished parenthesized option list.  get_previous_words treats a
> > > +        * completed parenthesized option list as one word, so the above test is
> > > +        * correct.  mode takes a string value ('replay', 'write', 'flush'),
> > > +        * timeout takes a string value, no_throw takes no value.
> > > +        */
> > >         else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
> > >                          !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
> > >         {
> > > -               /*
> > > -                * This fires if we're in an unfinished parenthesized option list.
> > > -                * get_previous_words treats a completed parenthesized option list as
> > > -                * one word, so the above test is correct.
> > > -                */
> > >                 if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
> > > -                       COMPLETE_WITH("timeout", "no_throw");
> > > -
> > > -               /*
> > > -                * timeout takes a string value, no_throw takes no value. We don't
> > > -                * offer completions for these values.
> > > -                */
> > > ```
> > >
> > > The new comment has lost the meaning of “We don’t offer completions for these values (timeout and no_throw)”, to be explicit, I feel we can retain the sentence.
> >
> >  The sentence is retained.
> >
> > > 5 - 0004
> > > ```
> > > +       my $isrecovery =
> > > +         $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
> > > +       chomp($isrecovery);
> > >         croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
> > >           . join(', ', keys(%valid_modes))
> > >           unless exists($valid_modes{$mode});
> > > @@ -3347,9 +3350,6 @@ sub wait_for_catchup
> > >         }
> > >         if (!defined($target_lsn))
> > >         {
> > > -               my $isrecovery =
> > > -                 $self->safe_psql('postgres', "SELECT pg_is_in_recovery()");
> > > -               chomp($isrecovery);
> > > ```
> > >
> > > I wonder why pull up pg_is_in_recovery to an early place and unconditionally call it?
> > >
> >
> > This seems unnecessary. I also realized that my earlier approach in
> > patch 4 may have been semantically incorrect — it could end up waiting
> > for the LSN to replay/write/flush on the node itself, rather than on
> > the downstream standby, which defeats the purpose of
> > wait_for_catchup(). Patch 4 attempts to address this by running WAIT
> > FOR LSN on the standby itself.
> >
> > Support for primary-flush waiting and the refactoring of existing
> > modes have been also incorporated in v8 following Alexander’s
> > feedback. The major updates are in patches 2 and 4. Please check.
> >
>
> Added WaitLSNTypeDesc to typedefs.list in v9 patch 2.
>

Run pgindent using the updated typedefs.list.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v10-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch (4.6K, ../../CABPTF7V=z1ok_LA6Df4YHMSFrO9t19CB4H1YTKLkLcTADW52HA@mail.gmail.com/2-v10-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch)
  download | inline diff:
From ad54cf3b65274450dfde3e4ea2898ac3a7352a12 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v10 4/4] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 59 +++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..51e5324bff3 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,53 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write' => 'standby_write',
+				'flush' => 'standby_flush',);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+			my $output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+
+			if ($output ne 'success')
+			{
+				# Fetch additional detail for debugging purposes
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN failed with status:
+	${output});
+				diag qq(Last pg_stat_replication contents:
+	${details});
+				croak "failed waiting for catchup";
+			}
+			print "done\n";
+			return;
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v10-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch (2.7K, ../../CABPTF7V=z1ok_LA6Df4YHMSFrO9t19CB4H1YTKLkLcTADW52HA@mail.gmail.com/3-v10-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch)
  download | inline diff:
From afc09c6893df66fa0348fff92b5999c9873d3812 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v10 3/4] Add tab completion for WAIT FOR LSN MODE option

Update psql tab completion to support the optional MODE option in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords. The MODE option controls whether
the wait is evaluated from the standby or primary perspective.

When MODE is specified, completion suggests the valid mode values:
standby_replay, standby_write, standby_flush, and primary_flush.
---
 src/bin/psql/tab-complete.in.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..62d87561169 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5355,8 +5355,10 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be: standby_replay | standby_write | standby_flush | primary_flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5369,21 +5371,25 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.
+	 *
+	 * 'mode' takes a string value ('standby_replay', 'standby_write',
+	 * 'standby_flush', 'primary_flush'). 'timeout' takes a string value, and
+	 * 'no_throw' takes no value. We do not offer completions for the *values*
+	 * of 'timeout' or 'no_throw'.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v10-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch (42.5K, ../../CABPTF7V=z1ok_LA6Df4YHMSFrO9t19CB4H1YTKLkLcTADW52HA@mail.gmail.com/4-v10-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 091eed383f1fc9503f423a9253634b4d477817e5 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v10 2/4] Add MODE option to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE option in the
WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
- 'standby_replay' (default): Wait for WAL to be replayed to the specified LSN
- 'standby_write': Wait for WAL to be written (received) to the specified LSN
- 'standby_flush': Wait for WAL to be flushed to disk at the specified LSN
- 'primary_flush': Wait for WAL to be flushed to disk on the primary server

The default mode is 'standby_replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands where options are specified as string values in the WITH clause.

Modes are explicitly named to distinguish between primary and standby operations:
- Standby modes ('standby_replay', 'standby_write', 'standby_flush') can only
  be used during recovery (on a standby server)
- Primary mode ('primary_flush') can only be used on a primary server

The 'standby_write' and 'standby_flush' modes are useful for scenarios where
applications need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete. The 'primary_flush' mode
allows waiting for WAL to be flushed on the primary server.

Also includes:
- Documentation updates for the new syntax and mode descriptions
- Test coverage for all four modes including error cases and concurrent waiters
- Wakeup logic in walreceiver for standby write/flush waiters
- Wakeup logic in WAL writer for primary flush waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 213 +++++++++---
 src/backend/access/transam/xlog.c       |  22 +-
 src/backend/commands/wait.c             |  96 +++++-
 src/backend/replication/walreceiver.c   |  18 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 411 ++++++++++++++++++++++--
 src/tools/pgindent/typedefs.list        |   1 +
 6 files changed, 674 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..df72b3327c8 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    standby_replay | standby_write | standby_flush | primary_flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,27 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+  </para>
+
+  <para>
+   On timeout, an error is emitted unless <literal>NO_THROW</literal>
+   is specified in the WITH clause. For standby modes
+   (<literal>standby_replay</literal>, <literal>standby_write</literal>,
+   <literal>standby_flush</literal>), an error is also emitted if the
+   server is promoted before the <parameter>lsn</parameter> is reached.
+   If <parameter>NO_THROW</parameter> is specified, the command returns
+   a status string instead of throwing errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +85,65 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>standby_replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>standby_replay</literal>: Wait for the LSN to be replayed
+            (applied to the database) on a standby server. After successful
+            completion, <function>pg_last_wal_replay_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_write</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and written to disk on a
+            standby server, but not yet flushed. This is faster than
+            <literal>standby_flush</literal> but provides weaker durability
+            guarantees since the data may still be in operating system
+            buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN. This mode can
+            only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_flush</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and flushed to disk on a
+            standby server. This provides a durability guarantee without
+            waiting for the WAL to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>primary_flush</literal>: Wait for the WAL containing the
+            LSN to be flushed to disk on a primary server. After successful
+            completion, <function>pg_current_wal_flush_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used on a primary server (not during recovery).
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +207,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +223,34 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>standby_replay</literal> mode
+   waits for the LSN to be replayed (applied to the database), which is
+   useful to achieve read-your-writes consistency while using an async
+   replica for reads and the primary for writes. The
+   <literal>standby_flush</literal> mode waits for the WAL to be flushed
+   to durable storage on the replica, providing a durability guarantee
+   without waiting for replay. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, which is
+   faster than flush but provides weaker durability guarantees. The
+   <literal>primary_flush</literal> mode waits for WAL to be flushed on
+   a primary server. In all cases, the <acronym>LSN</acronym> of the last
+   modification should be stored on the client application side or the
+   connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   The standby modes (<literal>standby_replay</literal>,
+   <literal>standby_write</literal>, <literal>standby_flush</literal>)
+   can only be used during recovery, and <literal>primary_flush</literal>
+   can only be used on a primary server. Using the wrong mode for the
+   current server state will result in an error. If a standby is promoted
+   while waiting with a standby mode, the command will return
+   <literal>not in recovery</literal> (or throw an error if
+   <literal>NO_THROW</literal> is not specified). Promotion creates a new
+   timeline, and the LSN being waited for may refer to WAL from the old
+   timeline.
   </para>
 
 </refsect1>
@@ -175,21 +259,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +284,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +295,43 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for flush on primary:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'primary_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +341,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdb92deac57..da96b627228 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2918,6 +2918,14 @@ XLogFlush(XLogRecPtr record)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * If we still haven't flushed to the request point then we have a
 	 * problem; most likely, the requested flush point is past end of XLOG.
@@ -3100,6 +3108,14 @@ XLogBackgroundFlush(void)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
@@ -6277,10 +6293,12 @@ StartupXLOG(void)
 	WakeupCheckpointer();
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..01df18140bd 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,18 +29,39 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* Mode name: "standby_replay",
+								 * "standby_write", "standby_flush",
+								 * "primary_flush" */
+	const char *verb;			/* Past participle: "replayed", "written",
+								 * "flushed" */
+} WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"standby_replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"standby_write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"standby_flush", "flushed"},
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = {"primary_flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +69,32 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "standby_replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "standby_write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "standby_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else if (pg_strcasecmp(mode_str, "primary_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_PRIMARY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for WAIT option \"MODE\": \"%s\"",
+								mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +154,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +187,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	/*
+	 * Validate that the requested mode matches the current server state.
+	 * Primary modes can only be used on a primary.
+	 */
+	if (lsnType == WAIT_LSN_TYPE_PRIMARY_FLUSH)
+	{
+		if (RecoveryInProgress())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("recovery is in progress"),
+					 errhint("Waiting for primary_flush can only be done on a primary server. "
+							 "Use standby_flush mode on a standby server.")));
+	}
+
+	/* Now wait for the LSN */
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +216,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +235,27 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+
 				if (PromoteIsTriggered())
 				{
+					XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..404d348da37 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,14 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1013,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for, notify the
+		 * waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..e41aad45e28 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,5 +1,6 @@
-# Checks waiting for the LSN replay on standby using
-# the WAIT FOR command.
+# Checks waiting for the LSN using the WAIT FOR command.
+# Tests standby modes (standby_replay/standby_write/standby_flush) on standby
+# and primary_flush mode on primary.
 use strict;
 use warnings FATAL => 'all';
 
@@ -7,6 +8,42 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql(
+		'postgres', qq[
+		SELECT pg_catalog.quote_literal(setting)
+		FROM pg_settings
+		WHERE name = 'primary_conninfo';
+	]);
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = $saved_primary_conninfo;
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +99,52 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with standby_write, standby_flush, and
+# primary_flush modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'standby_write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'standby_write'"
+);
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'standby_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'standby_flush'"
+);
+
+# Check primary_flush mode on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(51, 60))");
+my $lsn_primary_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_primary->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_primary_flush}' WITH (MODE 'primary_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '${lsn_primary_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"primary flushed WAL up to target LSN after WAIT FOR with MODE 'primary_flush'"
+);
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,14 +170,26 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
-# within another function, or inside a transaction with an isolation level
-# higher than READ COMMITTED.
+# 5. Check mode validation: standby modes error on primary, primary mode errors
+# on standby, and primary_flush works on primary.  Also check that WAIT FOR
+# triggers an error if called within another function or inside a transaction
+# with an isolation level higher than READ COMMITTED.
+
+# Test standby_flush on primary - should error
+$node_primary->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'standby_flush');",
+	stderr => \$stderr);
+ok($stderr =~ /recovery is not in progress/,
+	"get an error when running standby_flush on the primary");
 
-$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+# Test primary_flush on standby - should error
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'primary_flush');",
 	stderr => \$stderr);
-ok( $stderr =~ /recovery is not in progress/,
-	"get an error when running on the primary");
+ok($stderr =~ /recovery is in progress/,
+	"get an error when running primary_flush on the standby");
 
 $node_standby->psql(
 	'postgres',
@@ -125,7 +219,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,10 +302,26 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
-# psql sessions each waiting for a corresponding insertion.  When waiting is
-# finished, stored procedures logs if there are visible as many rows as
-# should be.
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "MODE": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'standby_replay', MODE 'standby_write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple standby_replay waiters.  We make 5
+# background psql sessions each waiting for a corresponding insertion.  When
+# waiting is finished, stored procedures logs if there are visible as many
+# rows as should be.
 $node_primary->safe_psql(
 	'postgres', qq[
 CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
@@ -225,8 +335,17 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
   END
 \$\$
 LANGUAGE plpgsql;
+
+CREATE FUNCTION log_wait_done(prefix text, i int) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '% %', prefix, i;
+  END
+\$\$
+LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +362,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +371,246 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple standby_replay waiters reported consistent data');
+
+# 7b. Check the scenario of multiple standby_write waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_write waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d');
+		SELECT log_wait_done('write_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_write waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple standby_flush waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_flush waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d');
+		SELECT log_wait_done('flush_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_flush waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed standby mode waiters (standby_replay,
+# standby_write, standby_flush) running concurrently.  We start 6 sessions:
+# 2 for each mode, all waiting for the same target LSN.  We stop the
+# walreceiver and pause replay to ensure all waiters block.  Then we resume
+# replay and restart the walreceiver to verify they unblock and complete
+# correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		SELECT log_wait_done('mixed_done', $i);
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 7e. Check the scenario of multiple primary_flush waiters on primary.
+# We start 5 background sessions waiting for different LSNs with primary_flush
+# mode.  Each waiter logs when done.
+my @primary_flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (400 + ${i});");
+	$primary_flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+my $primary_flush_log_offset = -s $node_primary->logfile;
+
+# Start primary_flush waiters
+my @primary_flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$primary_flush_sessions[$i] = $node_primary->background_psql('postgres');
+	$primary_flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d');
+		SELECT log_wait_done('primary_flush_done', $i);
+	]);
+}
+
+# The WAL should already be flushed, so waiters should complete quickly
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->wait_for_log("primary_flush_done $i",
+		$primary_flush_log_offset);
+	$primary_flush_sessions[$i]->quit;
+}
+
+# Verify on primary that WAL was flushed up to the target LSN
+$output = $node_primary->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '$primary_flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple primary_flush waiters: primary flushed WAL up to target LSN");
+
+# 8. Check that the standby promotion terminates all standby wait modes.  Start
+# waiting for unreachable LSNs with standby_replay, standby_write, and
+# standby_flush modes, then promote.  Check the log for the relevant error
+# messages.  Also, check that waiting for already replayed LSN doesn't cause
+# an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +620,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +645,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5c88fa92f4e..ab7149c5e62 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3305,6 +3305,7 @@ WaitLSNProcInfo
 WaitLSNResult
 WaitLSNState
 WaitLSNType
+WaitLSNTypeDesc
 WaitPMResult
 WaitStmt
 WalCloseMethod
-- 
2.51.0



  [application/octet-stream] v10-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch (11.5K, ../../CABPTF7V=z1ok_LA6Df4YHMSFrO9t19CB4H1YTKLkLcTADW52HA@mail.gmail.com/5-v10-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch)
  download | inline diff:
From 668956d0d0794c489167912d54d4c9c7bb237754 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v10 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 96 +++++++++++++++----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 ++-
 6 files changed, 93 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1b7ef589fc0..fdb92deac57 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6280,7 +6280,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 38b594d2170..2d81bb1a9a7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1856,8 +1856,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..5f4ff50cf38 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,47 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
+
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -302,6 +349,19 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Check if the given LSN type requires recovery to be in progress.
+ * Standby wait types (replay, write, flush) require recovery;
+ * primary wait types (flush) do not.
+ */
+static inline bool
+WaitLSNTypeRequiresRecovery(WaitLSNType t)
+{
+	return t == WAIT_LSN_TYPE_STANDBY_REPLAY ||
+		t == WAIT_LSN_TYPE_STANDBY_WRITE ||
+		t == WAIT_LSN_TYPE_STANDBY_FLUSH;
+}
+
 /*
  * Wait using MyLatch till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
@@ -341,13 +401,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (WaitLSNTypeRequiresRecovery(lsnType) && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +434,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index dcfadbd5aae..e62054585cb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..4cf13f0ccb3 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-30 03:14                                                                                                 ` Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Álvaro Herrera @ 2025-12-30 03:14 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Chao Li <[email protected]>; Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On 2025-Dec-27, Xuneng Zhou wrote:

> On Fri, Dec 26, 2025 at 4:25 PM Chao Li <[email protected]> wrote:

> > 2 - 0002
> > ```
> > +                       else
> > +                               ereport(ERROR,
> > +                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> > +                                                errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
> > +                                                               "MODE", mode_str),
> > ```
> >
> > I wonder why don’t we directly put MODE into the error message?
> 
> Yeah, putting MODE into the error message is cleaner. It's done in v8.

The reason not to do that (and also put WAIT in a separate string) is so
that the message is identicla to other messages and thus requires no
separate translation, specifically
  errmsg("unrecognized value for %s option \"%s\": \"%s\"", ...)

See commit 502e256f2262.  Please use that form.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"El sabio habla porque tiene algo que decir;
el tonto, porque tiene que decir algo" (Platon).





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2025-12-30 03:24                                                                                                   ` Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Chao Li @ 2025-12-30 03:24 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>



> On Dec 30, 2025, at 11:14, Álvaro Herrera <[email protected]> wrote:
> 
> On 2025-Dec-27, Xuneng Zhou wrote:
> 
>> On Fri, Dec 26, 2025 at 4:25 PM Chao Li <[email protected]> wrote:
> 
>>> 2 - 0002
>>> ```
>>> +                       else
>>> +                               ereport(ERROR,
>>> +                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
>>> +                                                errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
>>> +                                                               "MODE", mode_str),
>>> ```
>>> 
>>> I wonder why don’t we directly put MODE into the error message?
>> 
>> Yeah, putting MODE into the error message is cleaner. It's done in v8.
> 
> The reason not to do that (and also put WAIT in a separate string) is so
> that the message is identicla to other messages and thus requires no
> separate translation, specifically
>  errmsg("unrecognized value for %s option \"%s\": \"%s\"", ...)
> 
> See commit 502e256f2262.  Please use that form.
> 

To follow 502e256f2262, it should use “%s” for “WAIT” as well. I raised the comment because I saw “WAIT” is the format strings, thus “MODE” can be there as well.

So, we should do a similar change like:
```
-                                                errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
-                                                               opt->defname, p),
+                                                errmsg("unrecognized value for %s option \"%s\": \"%s\"",
+                                                               "EXPLAIN", opt->defname, p),
```

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
@ 2025-12-30 06:19                                                                                                     ` Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-30 06:19 UTC (permalink / raw)
  To: Chao Li <[email protected]>; Álvaro Herrera <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Dec 30, 2025 at 11:25 AM Chao Li <[email protected]> wrote:
>
>
>
> > On Dec 30, 2025, at 11:14, Álvaro Herrera <[email protected]> wrote:
> >
> > On 2025-Dec-27, Xuneng Zhou wrote:
> >
> >> On Fri, Dec 26, 2025 at 4:25 PM Chao Li <[email protected]> wrote:
> >
> >>> 2 - 0002
> >>> ```
> >>> +                       else
> >>> +                               ereport(ERROR,
> >>> +                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> >>> +                                                errmsg("unrecognized value for WAIT option \"%s\": \"%s\"",
> >>> +                                                               "MODE", mode_str),
> >>> ```
> >>>
> >>> I wonder why don’t we directly put MODE into the error message?
> >>
> >> Yeah, putting MODE into the error message is cleaner. It's done in v8.
> >
> > The reason not to do that (and also put WAIT in a separate string) is so
> > that the message is identicla to other messages and thus requires no
> > separate translation, specifically
> >  errmsg("unrecognized value for %s option \"%s\": \"%s\"", ...)
> >
> > See commit 502e256f2262.  Please use that form.
> >
>
> To follow 502e256f2262, it should use “%s” for “WAIT” as well. I raised the comment because I saw “WAIT” is the format strings, thus “MODE” can be there as well.
>
> So, we should do a similar change like:
> ```
> -                                                errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
> -                                                               opt->defname, p),
> +                                                errmsg("unrecognized value for %s option \"%s\": \"%s\"",
> +                                                               "EXPLAIN", opt->defname, p),
> ```
>

Thanks for raising this and clarifying the rationale. I've made the
modification per your input.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v11-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch (42.5K, ../../CABPTF7Xs-64GQNjmbimZNhj2YSKbBny+evz6=cp3X2fkJS+vMQ@mail.gmail.com/2-v11-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 6b15fa268bb26bdd81879da267f0d1ccab2c8093 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v11 2/4] Add MODE option to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE option in the
WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
- 'standby_replay' (default): Wait for WAL to be replayed to the specified LSN
- 'standby_write': Wait for WAL to be written (received) to the specified LSN
- 'standby_flush': Wait for WAL to be flushed to disk at the specified LSN
- 'primary_flush': Wait for WAL to be flushed to disk on the primary server

The default mode is 'standby_replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands where options are specified as string values in the WITH clause.

Modes are explicitly named to distinguish between primary and standby operations:
- Standby modes ('standby_replay', 'standby_write', 'standby_flush') can only
  be used during recovery (on a standby server)
- Primary mode ('primary_flush') can only be used on a primary server

The 'standby_write' and 'standby_flush' modes are useful for scenarios where
applications need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete. The 'primary_flush' mode
allows waiting for WAL to be flushed on the primary server.

Also includes:
- Documentation updates for the new syntax and mode descriptions
- Test coverage for all four modes including error cases and concurrent waiters
- Wakeup logic in walreceiver for standby write/flush waiters
- Wakeup logic in WAL writer for primary flush waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 213 +++++++++---
 src/backend/access/transam/xlog.c       |  22 +-
 src/backend/commands/wait.c             |  96 +++++-
 src/backend/replication/walreceiver.c   |  18 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 411 ++++++++++++++++++++++--
 src/tools/pgindent/typedefs.list        |   1 +
 6 files changed, 674 insertions(+), 87 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..df72b3327c8 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    standby_replay | standby_write | standby_flush | primary_flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,27 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+  </para>
+
+  <para>
+   On timeout, an error is emitted unless <literal>NO_THROW</literal>
+   is specified in the WITH clause. For standby modes
+   (<literal>standby_replay</literal>, <literal>standby_write</literal>,
+   <literal>standby_flush</literal>), an error is also emitted if the
+   server is promoted before the <parameter>lsn</parameter> is reached.
+   If <parameter>NO_THROW</parameter> is specified, the command returns
+   a status string instead of throwing errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +85,65 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>standby_replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>standby_replay</literal>: Wait for the LSN to be replayed
+            (applied to the database) on a standby server. After successful
+            completion, <function>pg_last_wal_replay_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_write</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and written to disk on a
+            standby server, but not yet flushed. This is faster than
+            <literal>standby_flush</literal> but provides weaker durability
+            guarantees since the data may still be in operating system
+            buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN. This mode can
+            only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_flush</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and flushed to disk on a
+            standby server. This provides a durability guarantee without
+            waiting for the WAL to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>primary_flush</literal>: Wait for the WAL containing the
+            LSN to be flushed to disk on a primary server. After successful
+            completion, <function>pg_current_wal_flush_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used on a primary server (not during recovery).
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +207,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +223,34 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>standby_replay</literal> mode
+   waits for the LSN to be replayed (applied to the database), which is
+   useful to achieve read-your-writes consistency while using an async
+   replica for reads and the primary for writes. The
+   <literal>standby_flush</literal> mode waits for the WAL to be flushed
+   to durable storage on the replica, providing a durability guarantee
+   without waiting for replay. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, which is
+   faster than flush but provides weaker durability guarantees. The
+   <literal>primary_flush</literal> mode waits for WAL to be flushed on
+   a primary server. In all cases, the <acronym>LSN</acronym> of the last
+   modification should be stored on the client application side or the
+   connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   The standby modes (<literal>standby_replay</literal>,
+   <literal>standby_write</literal>, <literal>standby_flush</literal>)
+   can only be used during recovery, and <literal>primary_flush</literal>
+   can only be used on a primary server. Using the wrong mode for the
+   current server state will result in an error. If a standby is promoted
+   while waiting with a standby mode, the command will return
+   <literal>not in recovery</literal> (or throw an error if
+   <literal>NO_THROW</literal> is not specified). Promotion creates a new
+   timeline, and the LSN being waited for may refer to WAL from the old
+   timeline.
   </para>
 
 </refsect1>
@@ -175,21 +259,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +284,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +295,43 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for flush on primary:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'primary_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +341,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdb92deac57..da96b627228 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2918,6 +2918,14 @@ XLogFlush(XLogRecPtr record)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * If we still haven't flushed to the request point then we have a
 	 * problem; most likely, the requested flush point is past end of XLOG.
@@ -3100,6 +3108,14 @@ XLogBackgroundFlush(void)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
@@ -6277,10 +6293,12 @@ StartupXLOG(void)
 	WakeupCheckpointer();
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..016e948eb77 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -28,18 +29,39 @@
 #include "utils/snapmgr.h"
 
 
+/*
+ * Type descriptor for WAIT FOR LSN wait types, used for error messages.
+ */
+typedef struct WaitLSNTypeDesc
+{
+	const char *noun;			/* Mode name: "standby_replay",
+								 * "standby_write", "standby_flush",
+								 * "primary_flush" */
+	const char *verb;			/* Past participle: "replayed", "written",
+								 * "flushed" */
+} WaitLSNTypeDesc;
+
+static const WaitLSNTypeDesc WaitLSNTypeDescs[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = {"standby_replay", "replayed"},
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = {"standby_write", "written"},
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = {"standby_flush", "flushed"},
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = {"primary_flush", "flushed"},
+};
+
 void
 ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 {
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +69,32 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "standby_replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "standby_write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "standby_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else if (pg_strcasecmp(mode_str, "primary_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_PRIMARY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for %s option \"%s\": \"%s\"",
+								"WAIT", defel->defname, mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +154,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +187,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	/*
+	 * Validate that the requested mode matches the current server state.
+	 * Primary modes can only be used on a primary.
+	 */
+	if (lsnType == WAIT_LSN_TYPE_PRIMARY_FLUSH)
+	{
+		if (RecoveryInProgress())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("recovery is in progress"),
+					 errhint("Waiting for primary_flush can only be done on a primary server. "
+							 "Use standby_flush mode on a standby server.")));
+	}
+
+	/* Now wait for the LSN */
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +216,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
+			{
+				const WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 				ereport(ERROR,
 						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
+						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
 							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+							   desc->verb,
+							   desc->noun,
+							   LSN_FORMAT_ARGS(currentLSN)));
+			}
 			else
 				result = "timeout";
 			break;
@@ -166,20 +235,27 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		case WAIT_LSN_RESULT_NOT_IN_RECOVERY:
 			if (throw)
 			{
+				const WaitLSNTypeDesc *desc = &WaitLSNTypeDescs[lsnType];
+
 				if (PromoteIsTriggered())
 				{
+					XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
+							errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN %X/%08X.",
 									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+									  desc->verb,
+									  desc->noun,
+									  LSN_FORMAT_ARGS(currentLSN)));
 				}
 				else
 					ereport(ERROR,
 							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+							errhint("Waiting for the %s LSN can only be executed during recovery.",
+									desc->noun));
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..404d348da37 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,14 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1013,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for, notify the
+		 * waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..b767b475ff7 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,5 +1,6 @@
-# Checks waiting for the LSN replay on standby using
-# the WAIT FOR command.
+# Checks waiting for the LSN using the WAIT FOR command.
+# Tests standby modes (standby_replay/standby_write/standby_flush) on standby
+# and primary_flush mode on primary.
 use strict;
 use warnings FATAL => 'all';
 
@@ -7,6 +8,42 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql(
+		'postgres', qq[
+		SELECT pg_catalog.quote_literal(setting)
+		FROM pg_settings
+		WHERE name = 'primary_conninfo';
+	]);
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = $saved_primary_conninfo;
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +99,52 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with standby_write, standby_flush, and
+# primary_flush modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'standby_write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'standby_write'"
+);
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'standby_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'standby_flush'"
+);
+
+# Check primary_flush mode on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(51, 60))");
+my $lsn_primary_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_primary->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_primary_flush}' WITH (MODE 'primary_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '${lsn_primary_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"primary flushed WAL up to target LSN after WAIT FOR with MODE 'primary_flush'"
+);
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,14 +170,26 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
-# within another function, or inside a transaction with an isolation level
-# higher than READ COMMITTED.
+# 5. Check mode validation: standby modes error on primary, primary mode errors
+# on standby, and primary_flush works on primary.  Also check that WAIT FOR
+# triggers an error if called within another function or inside a transaction
+# with an isolation level higher than READ COMMITTED.
+
+# Test standby_flush on primary - should error
+$node_primary->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'standby_flush');",
+	stderr => \$stderr);
+ok($stderr =~ /recovery is not in progress/,
+	"get an error when running standby_flush on the primary");
 
-$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+# Test primary_flush on standby - should error
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'primary_flush');",
 	stderr => \$stderr);
-ok( $stderr =~ /recovery is not in progress/,
-	"get an error when running on the primary");
+ok($stderr =~ /recovery is in progress/,
+	"get an error when running primary_flush on the standby");
 
 $node_standby->psql(
 	'postgres',
@@ -125,7 +219,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,10 +302,26 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
-# psql sessions each waiting for a corresponding insertion.  When waiting is
-# finished, stored procedures logs if there are visible as many rows as
-# should be.
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "mode": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'standby_replay', MODE 'standby_write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple standby_replay waiters.  We make 5
+# background psql sessions each waiting for a corresponding insertion.  When
+# waiting is finished, stored procedures logs if there are visible as many
+# rows as should be.
 $node_primary->safe_psql(
 	'postgres', qq[
 CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
@@ -225,8 +335,17 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
   END
 \$\$
 LANGUAGE plpgsql;
+
+CREATE FUNCTION log_wait_done(prefix text, i int) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '% %', prefix, i;
+  END
+\$\$
+LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +362,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +371,246 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple standby_replay waiters reported consistent data');
+
+# 7b. Check the scenario of multiple standby_write waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_write waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d');
+		SELECT log_wait_done('write_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_write waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple standby_flush waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_flush waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d');
+		SELECT log_wait_done('flush_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_flush waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed standby mode waiters (standby_replay,
+# standby_write, standby_flush) running concurrently.  We start 6 sessions:
+# 2 for each mode, all waiting for the same target LSN.  We stop the
+# walreceiver and pause replay to ensure all waiters block.  Then we resume
+# replay and restart the walreceiver to verify they unblock and complete
+# correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		SELECT log_wait_done('mixed_done', $i);
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 7e. Check the scenario of multiple primary_flush waiters on primary.
+# We start 5 background sessions waiting for different LSNs with primary_flush
+# mode.  Each waiter logs when done.
+my @primary_flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (400 + ${i});");
+	$primary_flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+my $primary_flush_log_offset = -s $node_primary->logfile;
+
+# Start primary_flush waiters
+my @primary_flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$primary_flush_sessions[$i] = $node_primary->background_psql('postgres');
+	$primary_flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d');
+		SELECT log_wait_done('primary_flush_done', $i);
+	]);
+}
+
+# The WAL should already be flushed, so waiters should complete quickly
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->wait_for_log("primary_flush_done $i",
+		$primary_flush_log_offset);
+	$primary_flush_sessions[$i]->quit;
+}
+
+# Verify on primary that WAL was flushed up to the target LSN
+$output = $node_primary->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '$primary_flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple primary_flush waiters: primary flushed WAL up to target LSN");
+
+# 8. Check that the standby promotion terminates all standby wait modes.  Start
+# waiting for unreachable LSNs with standby_replay, standby_write, and
+# standby_flush modes, then promote.  Check the log for the relevant error
+# messages.  Also, check that waiting for already replayed LSN doesn't cause
+# an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +620,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +645,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5c88fa92f4e..ab7149c5e62 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3305,6 +3305,7 @@ WaitLSNProcInfo
 WaitLSNResult
 WaitLSNState
 WaitLSNType
+WaitLSNTypeDesc
 WaitPMResult
 WaitStmt
 WalCloseMethod
-- 
2.51.0



  [application/octet-stream] v11-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch (11.5K, ../../CABPTF7Xs-64GQNjmbimZNhj2YSKbBny+evz6=cp3X2fkJS+vMQ@mail.gmail.com/3-v11-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch)
  download | inline diff:
From 668956d0d0794c489167912d54d4c9c7bb237754 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v11 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 96 +++++++++++++++----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 ++-
 6 files changed, 93 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1b7ef589fc0..fdb92deac57 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6280,7 +6280,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 38b594d2170..2d81bb1a9a7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1856,8 +1856,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..5f4ff50cf38 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,47 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
+
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -302,6 +349,19 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Check if the given LSN type requires recovery to be in progress.
+ * Standby wait types (replay, write, flush) require recovery;
+ * primary wait types (flush) do not.
+ */
+static inline bool
+WaitLSNTypeRequiresRecovery(WaitLSNType t)
+{
+	return t == WAIT_LSN_TYPE_STANDBY_REPLAY ||
+		t == WAIT_LSN_TYPE_STANDBY_WRITE ||
+		t == WAIT_LSN_TYPE_STANDBY_FLUSH;
+}
+
 /*
  * Wait using MyLatch till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
@@ -341,13 +401,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (WaitLSNTypeRequiresRecovery(lsnType) && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +434,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index dcfadbd5aae..e62054585cb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..4cf13f0ccb3 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v11-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch (2.7K, ../../CABPTF7Xs-64GQNjmbimZNhj2YSKbBny+evz6=cp3X2fkJS+vMQ@mail.gmail.com/4-v11-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch)
  download | inline diff:
From b2c1fac6ec41ec52d96628294c2d1c7f5c1191f3 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v11 3/4] Add tab completion for WAIT FOR LSN MODE option

Update psql tab completion to support the optional MODE option in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords. The MODE option controls whether
the wait is evaluated from the standby or primary perspective.

When MODE is specified, completion suggests the valid mode values:
standby_replay, standby_write, standby_flush, and primary_flush.
---
 src/bin/psql/tab-complete.in.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..62d87561169 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5355,8 +5355,10 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be: standby_replay | standby_write | standby_flush | primary_flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5369,21 +5371,25 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.
+	 *
+	 * 'mode' takes a string value ('standby_replay', 'standby_write',
+	 * 'standby_flush', 'primary_flush'). 'timeout' takes a string value, and
+	 * 'no_throw' takes no value. We do not offer completions for the *values*
+	 * of 'timeout' or 'no_throw'.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v11-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch (4.6K, ../../CABPTF7Xs-64GQNjmbimZNhj2YSKbBny+evz6=cp3X2fkJS+vMQ@mail.gmail.com/5-v11-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch)
  download | inline diff:
From 7134802e488caf98ae9ef33c09a10e21a5fa0fc3 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v11 4/4] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 59 +++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..51e5324bff3 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,53 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write' => 'standby_write',
+				'flush' => 'standby_flush',);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+			my $output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+
+			if ($output ne 'success')
+			{
+				# Fetch additional detail for debugging purposes
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN failed with status:
+	${output});
+				diag qq(Last pg_stat_replication contents:
+	${details});
+				croak "failed waiting for catchup";
+			}
+			print "done\n";
+			return;
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-01 17:16                                                                                                       ` Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Álvaro Herrera @ 2026-01-01 17:16 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Chao Li <[email protected]>; Alexander Korotkov <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>


In 0002 you have this kind of thing:

>  				ereport(ERROR,
>  						errcode(ERRCODE_QUERY_CANCELED),
> -						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
> +						errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
>  							   LSN_FORMAT_ARGS(lsn),
> -							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> +							   desc->verb,
> +							   desc->noun,
> +							   LSN_FORMAT_ARGS(currentLSN)));
> +			}


I'm afraid this technique doesn't work, for translatability reasons.
Your whole design of having a struct with ->verb and ->noun is not
workable (which is a pity, but you can't really fight this.) You need to
spell out the whole messages for each case, something like

if (lsntype == replay)
   ereport(ERROR,
           errcode(ERRCODE_QUERY_CANCELED),
           errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
else if (lsntype == flush)
    ereport( ... )

and so on.  This means four separate messages for translation for each
message your patch is adding, which is IMO the correct approach.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"...  In accounting terms this makes perfect sense.  To rational humans, it
is insane.  Welcome to IBM."                           (Robert X. Cringely)
https://www.cringely.com/2015/06/03/autodesks-john-walker-explained-hp-and-ibm-in-1991/





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2026-01-01 23:42                                                                                                         ` Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-01 23:42 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Jan 1, 2026 at 7:16 PM Álvaro Herrera <[email protected]> wrote:
> In 0002 you have this kind of thing:
>
> >                               ereport(ERROR,
> >                                               errcode(ERRCODE_QUERY_CANCELED),
> > -                                             errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
> > +                                             errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
> >                                                          LSN_FORMAT_ARGS(lsn),
> > -                                                        LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> > +                                                        desc->verb,
> > +                                                        desc->noun,
> > +                                                        LSN_FORMAT_ARGS(currentLSN)));
> > +                     }
>
>
> I'm afraid this technique doesn't work, for translatability reasons.
> Your whole design of having a struct with ->verb and ->noun is not
> workable (which is a pity, but you can't really fight this.) You need to
> spell out the whole messages for each case, something like
>
> if (lsntype == replay)
>    ereport(ERROR,
>            errcode(ERRCODE_QUERY_CANCELED),
>            errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
> else if (lsntype == flush)
>     ereport( ... )
>
> and so on.  This means four separate messages for translation for each
> message your patch is adding, which is IMO the correct approach.

+1
Thank you for catching this, Alvaro.  Yes, I think we need to get rid
of WaitLSNTypeDesc.  It's nice idea, but we support too many languages
to have something like this.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-01-02 09:17                                                                                                           ` Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-02 09:17 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; Álvaro Herrera <[email protected]>; +Cc: Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alvaro, Alexander,

On Fri, Jan 2, 2026 at 7:42 AM Alexander Korotkov <[email protected]> wrote:
>
> On Thu, Jan 1, 2026 at 7:16 PM Álvaro Herrera <[email protected]> wrote:
> > In 0002 you have this kind of thing:
> >
> > >                               ereport(ERROR,
> > >                                               errcode(ERRCODE_QUERY_CANCELED),
> > > -                                             errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
> > > +                                             errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
> > >                                                          LSN_FORMAT_ARGS(lsn),
> > > -                                                        LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> > > +                                                        desc->verb,
> > > +                                                        desc->noun,
> > > +                                                        LSN_FORMAT_ARGS(currentLSN)));
> > > +                     }
> >
> >
> > I'm afraid this technique doesn't work, for translatability reasons.
> > Your whole design of having a struct with ->verb and ->noun is not
> > workable (which is a pity, but you can't really fight this.) You need to
> > spell out the whole messages for each case, something like
> >
> > if (lsntype == replay)
> >    ereport(ERROR,
> >            errcode(ERRCODE_QUERY_CANCELED),
> >            errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
> > else if (lsntype == flush)
> >     ereport( ... )
> >
> > and so on.  This means four separate messages for translation for each
> > message your patch is adding, which is IMO the correct approach.
>
> +1
> Thank you for catching this, Alvaro.  Yes, I think we need to get rid
> of WaitLSNTypeDesc.  It's nice idea, but we support too many languages
> to have something like this.
>

Thanks for pointing this out. This approach doesn’t scale to multiple
languages. While switch statements are more verbose, the extra clarity
is justified to preserve proper internationalization. Please check the
updated v12.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v12-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch (2.7K, ../../CABPTF7UkwQZGx5ub731Q+=+rU8yx4ruqMdDt__L_dm9_32LsMw@mail.gmail.com/2-v12-0003-Add-tab-completion-for-WAIT-FOR-LSN-MODE-option.patch)
  download | inline diff:
From fa5cb59b09cbaff43ffd8b3c3d0b98ebf671bebc Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:00:25 +0800
Subject: [PATCH v12 3/4] Add tab completion for WAIT FOR LSN MODE option

Update psql tab completion to support the optional MODE option in
WAIT FOR LSN command. After specifying an LSN value, completion now
offers both MODE and WITH keywords. The MODE option controls whether
the wait is evaluated from the standby or primary perspective.

When MODE is specified, completion suggests the valid mode values:
standby_replay, standby_write, standby_flush, and primary_flush.
---
 src/bin/psql/tab-complete.in.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..62d87561169 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5355,8 +5355,10 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be: standby_replay | standby_write | standby_flush | primary_flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5369,21 +5371,25 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.
+	 *
+	 * 'mode' takes a string value ('standby_replay', 'standby_write',
+	 * 'standby_flush', 'primary_flush'). 'timeout' takes a string value, and
+	 * 'no_throw' takes no value. We do not offer completions for the *values*
+	 * of 'timeout' or 'no_throw'.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.51.0



  [application/octet-stream] v12-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch (11.5K, ../../CABPTF7UkwQZGx5ub731Q+=+rU8yx4ruqMdDt__L_dm9_32LsMw@mail.gmail.com/3-v12-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch)
  download | inline diff:
From 668956d0d0794c489167912d54d4c9c7bb237754 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v12 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes:
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to WaitLSNType
- Add GetCurrentLSNForWaitType() to retrieve current LSN for each wait type
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 96 +++++++++++++++----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 ++-
 6 files changed, 93 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1b7ef589fc0..fdb92deac57 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6280,7 +6280,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 38b594d2170..2d81bb1a9a7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1856,8 +1856,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6109381c0f0..5f4ff50cf38 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,47 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
+
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -302,6 +349,19 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Check if the given LSN type requires recovery to be in progress.
+ * Standby wait types (replay, write, flush) require recovery;
+ * primary wait types (flush) do not.
+ */
+static inline bool
+WaitLSNTypeRequiresRecovery(WaitLSNType t)
+{
+	return t == WAIT_LSN_TYPE_STANDBY_REPLAY ||
+		t == WAIT_LSN_TYPE_STANDBY_WRITE ||
+		t == WAIT_LSN_TYPE_STANDBY_FLUSH;
+}
+
 /*
  * Wait using MyLatch till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
@@ -341,13 +401,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (WaitLSNTypeRequiresRecovery(lsnType) && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +434,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index a37bddaefb2..dd2570cb787 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index dcfadbd5aae..e62054585cb 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 3e8fcbd9177..4cf13f0ccb3 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.51.0



  [application/octet-stream] v12-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch (4.6K, ../../CABPTF7UkwQZGx5ub731Q+=+rU8yx4ruqMdDt__L_dm9_32LsMw@mail.gmail.com/4-v12-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch)
  download | inline diff:
From cf6bea8a139e492281664e524a69be0e2cca17af Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 11:03:23 +0800
Subject: [PATCH v12 4/4] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 59 +++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 295988b8b87..51e5324bff3 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,53 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write' => 'standby_write',
+				'flush' => 'standby_flush',);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+			my $output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+
+			if ($output ne 'success')
+			{
+				# Fetch additional detail for debugging purposes
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN failed with status:
+	${output});
+				diag qq(Last pg_stat_replication contents:
+	${details});
+				croak "failed waiting for catchup";
+			}
+			print "done\n";
+			return;
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



  [application/octet-stream] v12-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch (44.4K, ../../CABPTF7UkwQZGx5ub731Q+=+rU8yx4ruqMdDt__L_dm9_32LsMw@mail.gmail.com/5-v12-0002-Add-MODE-option-to-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 3276142d426310850877eadbabe041d6d51e2c76 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:50:22 +0800
Subject: [PATCH v12 2/4] Add MODE option to WAIT FOR LSN command

Extend the WAIT FOR LSN command with an optional MODE option in the
WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
- 'standby_replay' (default): Wait for WAL to be replayed to the specified LSN
- 'standby_write': Wait for WAL to be written (received) to the specified LSN
- 'standby_flush': Wait for WAL to be flushed to disk at the specified LSN
- 'primary_flush': Wait for WAL to be flushed to disk on the primary server

The default mode is 'standby_replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands where options are specified as string values in the WITH clause.

Modes are explicitly named to distinguish between primary and standby operations:
- Standby modes ('standby_replay', 'standby_write', 'standby_flush') can only
  be used during recovery (on a standby server)
- Primary mode ('primary_flush') can only be used on a primary server

The 'standby_write' and 'standby_flush' modes are useful for scenarios where
applications need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete. The 'primary_flush' mode
allows waiting for WAL to be flushed on the primary server.

Also includes:
- Documentation updates for the new syntax and mode descriptions
- Test coverage for all four modes including error cases and concurrent waiters
- Wakeup logic in walreceiver for standby write/flush waiters
- Wakeup logic in WAL writer for primary flush waiters
---
 doc/src/sgml/ref/wait_for.sgml          | 213 +++++++++---
 src/backend/access/transam/xlog.c       |  22 +-
 src/backend/commands/wait.c             | 174 ++++++++--
 src/backend/replication/walreceiver.c   |  18 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 411 ++++++++++++++++++++++--
 5 files changed, 741 insertions(+), 97 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..df72b3327c8 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    standby_replay | standby_write | standby_flush | primary_flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,27 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+  </para>
+
+  <para>
+   On timeout, an error is emitted unless <literal>NO_THROW</literal>
+   is specified in the WITH clause. For standby modes
+   (<literal>standby_replay</literal>, <literal>standby_write</literal>,
+   <literal>standby_flush</literal>), an error is also emitted if the
+   server is promoted before the <parameter>lsn</parameter> is reached.
+   If <parameter>NO_THROW</parameter> is specified, the command returns
+   a status string instead of throwing errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +85,65 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>standby_replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>standby_replay</literal>: Wait for the LSN to be replayed
+            (applied to the database) on a standby server. After successful
+            completion, <function>pg_last_wal_replay_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_write</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and written to disk on a
+            standby server, but not yet flushed. This is faster than
+            <literal>standby_flush</literal> but provides weaker durability
+            guarantees since the data may still be in operating system
+            buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN. This mode can
+            only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_flush</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and flushed to disk on a
+            standby server. This provides a durability guarantee without
+            waiting for the WAL to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>primary_flush</literal>: Wait for the WAL containing the
+            LSN to be flushed to disk on a primary server. After successful
+            completion, <function>pg_current_wal_flush_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used on a primary server (not during recovery).
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +207,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +223,34 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>standby_replay</literal> mode
+   waits for the LSN to be replayed (applied to the database), which is
+   useful to achieve read-your-writes consistency while using an async
+   replica for reads and the primary for writes. The
+   <literal>standby_flush</literal> mode waits for the WAL to be flushed
+   to durable storage on the replica, providing a durability guarantee
+   without waiting for replay. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, which is
+   faster than flush but provides weaker durability guarantees. The
+   <literal>primary_flush</literal> mode waits for WAL to be flushed on
+   a primary server. In all cases, the <acronym>LSN</acronym> of the last
+   modification should be stored on the client application side or the
+   connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   The standby modes (<literal>standby_replay</literal>,
+   <literal>standby_write</literal>, <literal>standby_flush</literal>)
+   can only be used during recovery, and <literal>primary_flush</literal>
+   can only be used on a primary server. Using the wrong mode for the
+   current server state will result in an error. If a standby is promoted
+   while waiting with a standby mode, the command will return
+   <literal>not in recovery</literal> (or throw an error if
+   <literal>NO_THROW</literal> is not specified). Promotion creates a new
+   timeline, and the LSN being waited for may refer to WAL from the old
+   timeline.
   </para>
 
 </refsect1>
@@ -175,21 +259,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +284,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +295,43 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for flush on primary:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'primary_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +341,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdb92deac57..da96b627228 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2918,6 +2918,14 @@ XLogFlush(XLogRecPtr record)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * If we still haven't flushed to the request point then we have a
 	 * problem; most likely, the requested flush point is past end of XLOG.
@@ -3100,6 +3108,14 @@ XLogBackgroundFlush(void)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
@@ -6277,10 +6293,12 @@ StartupXLOG(void)
 	WakeupCheckpointer();
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index dd2570cb787..54f2df2425f 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -34,12 +35,14 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +50,32 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "standby_replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "standby_write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "standby_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else if (pg_strcasecmp(mode_str, "primary_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_PRIMARY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for %s option \"%s\": \"%s\"",
+								"WAIT", defel->defname, mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +135,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +168,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	/*
+	 * Validate that the requested mode matches the current server state.
+	 * Primary modes can only be used on a primary.
+	 */
+	if (lsnType == WAIT_LSN_TYPE_PRIMARY_FLUSH)
+	{
+		if (RecoveryInProgress())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("recovery is in progress"),
+					 errhint("Waiting for primary_flush can only be done on a primary server. "
+							 "Use standby_flush mode on a standby server.")));
+	}
+
+	/* Now wait for the LSN */
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +197,48 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
-				ereport(ERROR,
-						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
-							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			{
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
+				switch (lsnType)
+				{
+					case WAIT_LSN_TYPE_STANDBY_REPLAY:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					case WAIT_LSN_TYPE_STANDBY_WRITE:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be written; current standby_write LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					case WAIT_LSN_TYPE_STANDBY_FLUSH:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be flushed; current standby_flush LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be flushed; current primary_flush LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					default:
+						elog(ERROR, "unexpected wait LSN type %d", lsnType);
+						pg_unreachable();
+				}
+			}
 			else
 				result = "timeout";
 			break;
@@ -168,18 +248,72 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 			{
 				if (PromoteIsTriggered())
 				{
-					ereport(ERROR,
-							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
-									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+					XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
+					switch (lsnType)
+					{
+						case WAIT_LSN_TYPE_STANDBY_REPLAY:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errdetail("Recovery ended before target LSN %X/%08X was replayed; last standby_replay LSN %X/%08X.",
+											  LSN_FORMAT_ARGS(lsn),
+											  LSN_FORMAT_ARGS(currentLSN)));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_WRITE:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errdetail("Recovery ended before target LSN %X/%08X was written; last standby_write LSN %X/%08X.",
+											  LSN_FORMAT_ARGS(lsn),
+											  LSN_FORMAT_ARGS(currentLSN)));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_FLUSH:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errdetail("Recovery ended before target LSN %X/%08X was flushed; last standby_flush LSN %X/%08X.",
+											  LSN_FORMAT_ARGS(lsn),
+											  LSN_FORMAT_ARGS(currentLSN)));
+							break;
+
+						default:
+							elog(ERROR, "unexpected wait LSN type %d", lsnType);
+							pg_unreachable();
+					}
 				}
 				else
-					ereport(ERROR,
-							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+				{
+					switch (lsnType)
+					{
+						case WAIT_LSN_TYPE_STANDBY_REPLAY:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errhint("Waiting for the standby_replay LSN can only be executed during recovery."));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_WRITE:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errhint("Waiting for the standby_write LSN can only be executed during recovery."));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_FLUSH:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errhint("Waiting for the standby_flush LSN can only be executed during recovery."));
+							break;
+
+						default:
+							elog(ERROR, "unexpected wait LSN type %d", lsnType);
+							pg_unreachable();
+					}
+				}
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac802ae85b4..404d348da37 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,14 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1013,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for, notify the
+		 * waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..b767b475ff7 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,5 +1,6 @@
-# Checks waiting for the LSN replay on standby using
-# the WAIT FOR command.
+# Checks waiting for the LSN using the WAIT FOR command.
+# Tests standby modes (standby_replay/standby_write/standby_flush) on standby
+# and primary_flush mode on primary.
 use strict;
 use warnings FATAL => 'all';
 
@@ -7,6 +8,42 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql(
+		'postgres', qq[
+		SELECT pg_catalog.quote_literal(setting)
+		FROM pg_settings
+		WHERE name = 'primary_conninfo';
+	]);
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = $saved_primary_conninfo;
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +99,52 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with standby_write, standby_flush, and
+# primary_flush modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'standby_write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'standby_write'"
+);
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'standby_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'standby_flush'"
+);
+
+# Check primary_flush mode on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(51, 60))");
+my $lsn_primary_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_primary->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_primary_flush}' WITH (MODE 'primary_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '${lsn_primary_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"primary flushed WAL up to target LSN after WAIT FOR with MODE 'primary_flush'"
+);
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,14 +170,26 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
-# within another function, or inside a transaction with an isolation level
-# higher than READ COMMITTED.
+# 5. Check mode validation: standby modes error on primary, primary mode errors
+# on standby, and primary_flush works on primary.  Also check that WAIT FOR
+# triggers an error if called within another function or inside a transaction
+# with an isolation level higher than READ COMMITTED.
+
+# Test standby_flush on primary - should error
+$node_primary->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'standby_flush');",
+	stderr => \$stderr);
+ok($stderr =~ /recovery is not in progress/,
+	"get an error when running standby_flush on the primary");
 
-$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+# Test primary_flush on standby - should error
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'primary_flush');",
 	stderr => \$stderr);
-ok( $stderr =~ /recovery is not in progress/,
-	"get an error when running on the primary");
+ok($stderr =~ /recovery is in progress/,
+	"get an error when running primary_flush on the standby");
 
 $node_standby->psql(
 	'postgres',
@@ -125,7 +219,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,10 +302,26 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
-# psql sessions each waiting for a corresponding insertion.  When waiting is
-# finished, stored procedures logs if there are visible as many rows as
-# should be.
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "mode": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'standby_replay', MODE 'standby_write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple standby_replay waiters.  We make 5
+# background psql sessions each waiting for a corresponding insertion.  When
+# waiting is finished, stored procedures logs if there are visible as many
+# rows as should be.
 $node_primary->safe_psql(
 	'postgres', qq[
 CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
@@ -225,8 +335,17 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
   END
 \$\$
 LANGUAGE plpgsql;
+
+CREATE FUNCTION log_wait_done(prefix text, i int) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '% %', prefix, i;
+  END
+\$\$
+LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +362,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +371,246 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple standby_replay waiters reported consistent data');
+
+# 7b. Check the scenario of multiple standby_write waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_write waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d');
+		SELECT log_wait_done('write_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_write waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple standby_flush waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_flush waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d');
+		SELECT log_wait_done('flush_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_flush waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed standby mode waiters (standby_replay,
+# standby_write, standby_flush) running concurrently.  We start 6 sessions:
+# 2 for each mode, all waiting for the same target LSN.  We stop the
+# walreceiver and pause replay to ensure all waiters block.  Then we resume
+# replay and restart the walreceiver to verify they unblock and complete
+# correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		SELECT log_wait_done('mixed_done', $i);
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 7e. Check the scenario of multiple primary_flush waiters on primary.
+# We start 5 background sessions waiting for different LSNs with primary_flush
+# mode.  Each waiter logs when done.
+my @primary_flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (400 + ${i});");
+	$primary_flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+my $primary_flush_log_offset = -s $node_primary->logfile;
+
+# Start primary_flush waiters
+my @primary_flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$primary_flush_sessions[$i] = $node_primary->background_psql('postgres');
+	$primary_flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d');
+		SELECT log_wait_done('primary_flush_done', $i);
+	]);
+}
+
+# The WAL should already be flushed, so waiters should complete quickly
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->wait_for_log("primary_flush_done $i",
+		$primary_flush_log_offset);
+	$primary_flush_sessions[$i]->quit;
+}
+
+# Verify on primary that WAL was flushed up to the target LSN
+$output = $node_primary->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '$primary_flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple primary_flush waiters: primary flushed WAL up to target LSN");
+
+# 8. Check that the standby promotion terminates all standby wait modes.  Start
+# waiting for unreachable LSNs with standby_replay, standby_write, and
+# standby_flush modes, then promote.  Check the log for the relevant error
+# messages.  Also, check that waiting for already replayed LSN doesn't cause
+# an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +620,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +645,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-02 22:53                                                                                                             ` Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-02 22:53 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi, Xuneng!

On Fri, Jan 2, 2026 at 11:17 AM Xuneng Zhou <[email protected]> wrote:
> On Fri, Jan 2, 2026 at 7:42 AM Alexander Korotkov <[email protected]> wrote:
> >
> > On Thu, Jan 1, 2026 at 7:16 PM Álvaro Herrera <[email protected]> wrote:
> > > In 0002 you have this kind of thing:
> > >
> > > >                               ereport(ERROR,
> > > >                                               errcode(ERRCODE_QUERY_CANCELED),
> > > > -                                             errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
> > > > +                                             errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
> > > >                                                          LSN_FORMAT_ARGS(lsn),
> > > > -                                                        LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> > > > +                                                        desc->verb,
> > > > +                                                        desc->noun,
> > > > +                                                        LSN_FORMAT_ARGS(currentLSN)));
> > > > +                     }
> > >
> > >
> > > I'm afraid this technique doesn't work, for translatability reasons.
> > > Your whole design of having a struct with ->verb and ->noun is not
> > > workable (which is a pity, but you can't really fight this.) You need to
> > > spell out the whole messages for each case, something like
> > >
> > > if (lsntype == replay)
> > >    ereport(ERROR,
> > >            errcode(ERRCODE_QUERY_CANCELED),
> > >            errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
> > > else if (lsntype == flush)
> > >     ereport( ... )
> > >
> > > and so on.  This means four separate messages for translation for each
> > > message your patch is adding, which is IMO the correct approach.
> >
> > +1
> > Thank you for catching this, Alvaro.  Yes, I think we need to get rid
> > of WaitLSNTypeDesc.  It's nice idea, but we support too many languages
> > to have something like this.
> >
>
> Thanks for pointing this out. This approach doesn’t scale to multiple
> languages. While switch statements are more verbose, the extra clarity
> is justified to preserve proper internationalization. Please check the
> updated v12.

I've corrected the patchset.  Mostly changed just comments, formatting
etc.  I'm going to push it if no objections.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v13-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch (11.8K, ../../CAPpHfds-KiZRuCruc0jHxLSxLqzKcHJGwOFFA0b_RgaJvtUOEQ@mail.gmail.com/2-v13-0001-Extend-xlogwait-infrastructure-with-write-and-fl.patch)
  download | inline diff:
From 3f7b1deaae59f45d8e049cf3b95ac7716ab38471 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 16 Dec 2025 10:21:36 +0800
Subject: [PATCH v13 1/4] Extend xlogwait infrastructure with write and flush
 wait types

Add support for waiting on WAL write and flush LSNs in addition to the
existing replay LSN wait type. This provides the foundation for
extending the WAIT FOR command with MODE parameter.

Key changes are following.
- Add WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH to
  WaitLSNType.
- Add GetCurrentLSNForWaitType() to retrieve the current LSN for each wait
  type.
- Add new wait events WAIT_EVENT_WAIT_FOR_WAL_WRITE and
  WAIT_EVENT_WAIT_FOR_WAL_FLUSH for pg_stat_activity visibility.
- Update WaitForLSN() to use GetCurrentLSNForWaitType() internally.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/backend/access/transam/xlog.c             |  2 +-
 src/backend/access/transam/xlogrecovery.c     |  4 +-
 src/backend/access/transam/xlogwait.c         | 96 +++++++++++++++----
 src/backend/commands/wait.c                   |  2 +-
 .../utils/activity/wait_event_names.txt       |  3 +-
 src/include/access/xlogwait.h                 | 14 ++-
 6 files changed, 93 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e71b6e21123..05ac7c5f7f8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6280,7 +6280,7 @@ StartupXLOG(void)
 	 * Wake up all waiters for replay LSN.  They need to report an error that
 	 * recovery was ended before reaching the target LSN.
 	 */
-	WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a21ac48c9fe..0b5f871abe7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1856,8 +1856,8 @@ PerformWalRecovery(void)
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
+				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6c2bda763e2..5020ae1e52d 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -12,25 +12,30 @@
  *		This file implements waiting for WAL operations to reach specific LSNs
  *		on both physical standby and primary servers. The core idea is simple:
  *		every process that wants to wait publishes the LSN it needs to the
- *		shared memory, and the appropriate process (startup on standby, or
- *		WAL writer/backend on primary) wakes it once that LSN has been reached.
+ *		shared memory, and the appropriate process (startup on standby,
+ *		walreceiver on standby, or WAL writer/backend on primary) wakes it
+ *		once that LSN has been reached.
  *
  *		The shared memory used by this module comprises a procInfos
  *		per-backend array with the information of the awaited LSN for each
  *		of the backend processes.  The elements of that array are organized
- *		into a pairing heap waitersHeap, which allows for very fast finding
- *		of the least awaited LSN.
+ *		into pairing heaps (waitersHeap), one for each WaitLSNType, which
+ *		allows for very fast finding of the least awaited LSN for each type.
  *
- *		In addition, the least-awaited LSN is cached as minWaitedLSN.  The
- *		waiter process publishes information about itself to the shared
- *		memory and waits on the latch until it is woken up by the appropriate
- *		process, standby is promoted, or the postmaster	dies.  Then, it cleans
- *		information about itself in the shared memory.
+ *		In addition, the least-awaited LSN for each type is cached in the
+ *		minWaitedLSN array.  The waiter process publishes information about
+ *		itself to the shared memory and waits on the latch until it is woken
+ *		up by the appropriate process, standby is promoted, or the postmaster
+ *		dies.  Then, it cleans information about itself in the shared memory.
  *
- *		On standby servers: After replaying a WAL record, the startup process
- *		first performs a fast path check minWaitedLSN > replayLSN.  If this
- *		check is negative, it checks waitersHeap and wakes up the backend
- *		whose awaited LSNs are reached.
+ *		On standby servers:
+ *		- After replaying a WAL record, the startup process performs a fast
+ *		  path check minWaitedLSN[REPLAY] > replayLSN.  If this check is
+ *		  negative, it checks waitersHeap[REPLAY] and wakes up the backends
+ *		  whose awaited LSNs are reached.
+ *		- After receiving WAL, the walreceiver process performs similar checks
+ *		  against the flush and write LSNs, waking up waiters in the FLUSH
+ *		  and WRITE heaps, respectively.
  *
  *		On primary servers: After flushing WAL, the WAL writer or backend
  *		process performs a similar check against the flush LSN and wakes up
@@ -49,6 +54,7 @@
 #include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/walreceiver.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -62,6 +68,47 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+/*
+ * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * the wait in pg_stat_activity.
+ */
+static const uint32 WaitLSNWaitEvents[] = {
+	[WAIT_LSN_TYPE_STANDBY_REPLAY] = WAIT_EVENT_WAIT_FOR_WAL_REPLAY,
+	[WAIT_LSN_TYPE_STANDBY_WRITE] = WAIT_EVENT_WAIT_FOR_WAL_WRITE,
+	[WAIT_LSN_TYPE_STANDBY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+	[WAIT_LSN_TYPE_PRIMARY_FLUSH] = WAIT_EVENT_WAIT_FOR_WAL_FLUSH,
+};
+
+StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
+				 "WaitLSNWaitEvents must match WaitLSNType enum");
+
+/*
+ * Get the current LSN for the specified wait type.
+ */
+XLogRecPtr
+GetCurrentLSNForWaitType(WaitLSNType lsnType)
+{
+	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
+
+	switch (lsnType)
+	{
+		case WAIT_LSN_TYPE_STANDBY_REPLAY:
+			return GetXLogReplayRecPtr(NULL);
+
+		case WAIT_LSN_TYPE_STANDBY_WRITE:
+			return GetWalRcvWriteRecPtr();
+
+		case WAIT_LSN_TYPE_STANDBY_FLUSH:
+			return GetWalRcvFlushRecPtr(NULL, NULL);
+
+		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+			return GetFlushRecPtr(NULL);
+	}
+
+	elog(ERROR, "invalid LSN wait type: %d", lsnType);
+	pg_unreachable();
+}
+
 /* Report the amount of shared memory space needed for WaitLSNState. */
 Size
 WaitLSNShmemSize(void)
@@ -302,6 +349,19 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Check if the given LSN type requires recovery to be in progress.
+ * Standby wait types (replay, write, flush) require recovery;
+ * primary wait types (flush) do not.
+ */
+static inline bool
+WaitLSNTypeRequiresRecovery(WaitLSNType t)
+{
+	return t == WAIT_LSN_TYPE_STANDBY_REPLAY ||
+		t == WAIT_LSN_TYPE_STANDBY_WRITE ||
+		t == WAIT_LSN_TYPE_STANDBY_FLUSH;
+}
+
 /*
  * Wait using MyLatch till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
@@ -341,13 +401,11 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		int			rc;
 		long		delay_ms = -1;
 
-		if (lsnType == WAIT_LSN_TYPE_REPLAY)
-			currentLSN = GetXLogReplayRecPtr(NULL);
-		else
-			currentLSN = GetFlushRecPtr(NULL);
+		/* Get current LSN for the wait type */
+		currentLSN = GetCurrentLSNForWaitType(lsnType);
 
 		/* Check that recovery is still in-progress */
-		if (lsnType == WAIT_LSN_TYPE_REPLAY && !RecoveryInProgress())
+		if (WaitLSNTypeRequiresRecovery(lsnType) && !RecoveryInProgress())
 		{
 			/*
 			 * Recovery was ended, but check if target LSN was already
@@ -376,7 +434,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 		CHECK_FOR_INTERRUPTS();
 
 		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   (lsnType == WAIT_LSN_TYPE_REPLAY) ? WAIT_EVENT_WAIT_FOR_WAL_REPLAY : WAIT_EVENT_WAIT_FOR_WAL_FLUSH);
+					   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index d43dfd642d6..4867f59691e 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -140,7 +140,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
+	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 43d870dbcf1..3299de23bb3 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -89,8 +89,9 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for WAL to be received and flushed by the physical standby."
-WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary."
+WAIT_FOR_WAL_FLUSH	"Waiting for WAL flush to reach a target LSN on a primary or standby."
 WAIT_FOR_WAL_REPLAY	"Waiting for WAL replay to reach a target LSN on a standby."
+WAIT_FOR_WAL_WRITE	"Waiting for WAL write to reach a target LSN on a standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index b5fd3e74f1c..d12531d32b8 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * xlogwait.h
- *	  Declarations for LSN replay waiting routines.
+ *	  Declarations for WAL flush, write, and replay waiting routines.
  *
  * Copyright (c) 2025-2026, PostgreSQL Global Development Group
  *
@@ -35,11 +35,16 @@ typedef enum
  */
 typedef enum WaitLSNType
 {
-	WAIT_LSN_TYPE_REPLAY,		/* Waiting for replay on standby */
-	WAIT_LSN_TYPE_FLUSH,		/* Waiting for flush on primary */
+	/* Standby wait types (walreceiver/startup wakes) */
+	WAIT_LSN_TYPE_STANDBY_REPLAY,
+	WAIT_LSN_TYPE_STANDBY_WRITE,
+	WAIT_LSN_TYPE_STANDBY_FLUSH,
+
+	/* Primary wait types (WAL writer/backends wake) */
+	WAIT_LSN_TYPE_PRIMARY_FLUSH,
 } WaitLSNType;
 
-#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_FLUSH + 1)
+#define WAIT_LSN_TYPE_COUNT (WAIT_LSN_TYPE_PRIMARY_FLUSH + 1)
 
 /*
  * WaitLSNProcInfo - the shared memory structure representing information
@@ -97,6 +102,7 @@ extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
 extern Size WaitLSNShmemSize(void);
 extern void WaitLSNShmemInit(void);
+extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
 extern WaitLSNResult WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN,
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch (4.9K, ../../CAPpHfds-KiZRuCruc0jHxLSxLqzKcHJGwOFFA0b_RgaJvtUOEQ@mail.gmail.com/3-v13-0004-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait.patch)
  download | inline diff:
From 5827bd2d978757e910f2bf00f8e2006abc563d24 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 3 Jan 2026 00:49:10 +0200
Subject: [PATCH v13 4/4] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 59 +++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 955dfc0e7f8..a28ea89aa10 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,53 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write' => 'standby_write',
+				'flush' => 'standby_flush',);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+			my $output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+
+			if ($output ne 'success')
+			{
+				# Fetch additional detail for debugging purposes
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN failed with status:
+	${output});
+				diag qq(Last pg_stat_replication contents:
+	${details});
+				croak "failed waiting for catchup";
+			}
+			print "done\n";
+			return;
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0003-Add-tab-completion-for-the-WAIT-FOR-LSN-MODE-opt.patch (3.0K, ../../CAPpHfds-KiZRuCruc0jHxLSxLqzKcHJGwOFFA0b_RgaJvtUOEQ@mail.gmail.com/4-v13-0003-Add-tab-completion-for-the-WAIT-FOR-LSN-MODE-opt.patch)
  download | inline diff:
From 1d1ee0be7975a94e0f062a96e77be11322f335fa Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 3 Jan 2026 00:42:32 +0200
Subject: [PATCH v13 3/4] Add tab completion for the WAIT FOR LSN MODE option

Update psql tab completion to support the optional MODE option in the
WAIT FOR LSN command.  After specifying an LSN value, completion now offers
both MODE and WITH keywords.  The MODE option specifies which LSN type to wait
for.  In particular, it controls whether the wait is evaluated from the
standby or primary perspective.

When MODE is specified, the completion suggests the valid mode values:
standby_replay, standby_write, standby_flush, and primary_flush.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/bin/psql/tab-complete.in.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index d81f2fcdbe6..06edea98f06 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5355,8 +5355,11 @@ match_previous_words(int pattern_id,
 /*
  * WAIT FOR LSN '<lsn>' [ WITH ( option [, ...] ) ]
  * where option can be:
+ *   MODE '<mode>'
  *   TIMEOUT '<timeout>'
  *   NO_THROW
+ * and mode can be:
+ *   standby_replay | standby_write | standby_flush | primary_flush
  */
 	else if (Matches("WAIT"))
 		COMPLETE_WITH("FOR");
@@ -5369,21 +5372,24 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("WITH");
 	else if (Matches("WAIT", "FOR", "LSN", MatchAny, "WITH"))
 		COMPLETE_WITH("(");
+
+	/*
+	 * Handle parenthesized option list.  This fires when we're in an
+	 * unfinished parenthesized option list.  get_previous_words treats a
+	 * completed parenthesized option list as one word, so the above test is
+	 * correct.
+	 *
+	 * 'mode' takes a string value (one of the listed above), 'timeout' takes
+	 * a string value, and 'no_throw' takes no value.  We do not offer
+	 * completions for the *values* of 'timeout' or 'no_throw'.
+	 */
 	else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*") &&
 			 !HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", "(*)"))
 	{
-		/*
-		 * This fires if we're in an unfinished parenthesized option list.
-		 * get_previous_words treats a completed parenthesized option list as
-		 * one word, so the above test is correct.
-		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("timeout", "no_throw");
-
-		/*
-		 * timeout takes a string value, no_throw takes no value. We don't
-		 * offer completions for these values.
-		 */
+			COMPLETE_WITH("mode", "timeout", "no_throw");
+		else if (TailMatches("mode"))
+			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
 /* WITH [RECURSIVE] */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0002-Add-the-MODE-option-to-the-WAIT-FOR-LSN-command.patch (44.8K, ../../CAPpHfds-KiZRuCruc0jHxLSxLqzKcHJGwOFFA0b_RgaJvtUOEQ@mail.gmail.com/5-v13-0002-Add-the-MODE-option-to-the-WAIT-FOR-LSN-command.patch)
  download | inline diff:
From 4ff3736d2becbff9931ec571098f8ca44081b18c Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 3 Jan 2026 00:38:47 +0200
Subject: [PATCH v13 2/4] Add the MODE option to the WAIT FOR LSN command

This commit extends the WAIT FOR LSN command with an optional MODE option in
the WITH clause that specifies which LSN type to wait for:

  WAIT FOR LSN '<lsn>' [WITH (MODE '<mode>', ...)]

where mode can be:
 - 'standby_replay' (default): Wait for WAL to be replayed to the specified
   LSN,
 - 'standby_write': Wait for WAL to be written (received) to the specified
   LSN,
 - 'standby_flush': Wait for WAL to be flushed to disk at the specified LSN,
 - 'primary_flush': Wait for WAL to be flushed to disk on the primary server.

The default mode is 'standby_replay', matching the original behavior when MODE
is not specified. This follows the pattern used by COPY and EXPLAIN
commands, where options are specified as string values in the WITH clause.

Modes are explicitly named to distinguish between primary and standby
operations:
- Standby modes ('standby_replay', 'standby_write', 'standby_flush') can only
  be used during recovery (on a standby server),
- Primary mode ('primary_flush') can only be used on a primary server.

The 'standby_write' and 'standby_flush' modes are useful for scenarios where
applications need to ensure WAL has been received or persisted on the standby
without necessarily waiting for replay to complete. The 'primary_flush' mode
allows waiting for WAL to be flushed on the primary server.

This commit also includes includes:
- Documentation updates for the new syntax and mode descriptions,
- Test coverage for all four modes, including error cases and concurrent
  waiters,
- Wakeup logic in walreceiver for standby write/flush waiters,
- Wakeup logic in WAL writer for primary flush waiters.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 213 +++++++++---
 src/backend/access/transam/xlog.c       |  22 +-
 src/backend/commands/wait.c             | 174 ++++++++--
 src/backend/replication/walreceiver.c   |  18 ++
 src/test/recovery/t/049_wait_for_lsn.pl | 411 ++++++++++++++++++++++--
 5 files changed, 741 insertions(+), 97 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 3b8e842d1de..df72b3327c8 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -16,17 +16,23 @@ PostgreSQL documentation
 
  <refnamediv>
   <refname>WAIT FOR</refname>
-  <refpurpose>wait for target <acronym>LSN</acronym> to be replayed, optionally with a timeout</refpurpose>
+  <refpurpose>wait for WAL to reach a target <acronym>LSN</acronym></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
+    [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
 
+    MODE '<replaceable class="parameter">mode</replaceable>'
     TIMEOUT '<replaceable class="parameter">timeout</replaceable>'
     NO_THROW
+
+<phrase>and <replaceable class="parameter">mode</replaceable> can be:</phrase>
+
+    standby_replay | standby_write | standby_flush | primary_flush
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,20 +40,27 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Description</title>
 
   <para>
-    Waits until recovery replays <parameter>lsn</parameter>.
-    If no <parameter>timeout</parameter> is specified or it is set to
-    zero, this command waits indefinitely for the
-    <parameter>lsn</parameter>.
-    On timeout, or if the server is promoted before
-    <parameter>lsn</parameter> is reached, an error is emitted,
-    unless <literal>NO_THROW</literal> is specified in the WITH clause.
-    If <parameter>NO_THROW</parameter> is specified, then the command
-    doesn't throw errors.
+   Waits until the specified <parameter>lsn</parameter> is reached
+   according to the specified <parameter>mode</parameter>,
+   which determines whether to wait for WAL to be written, flushed, or replayed.
+   If no <parameter>timeout</parameter> is specified or it is set to
+   zero, this command waits indefinitely for the
+   <parameter>lsn</parameter>.
+  </para>
+
+  <para>
+   On timeout, an error is emitted unless <literal>NO_THROW</literal>
+   is specified in the WITH clause. For standby modes
+   (<literal>standby_replay</literal>, <literal>standby_write</literal>,
+   <literal>standby_flush</literal>), an error is also emitted if the
+   server is promoted before the <parameter>lsn</parameter> is reached.
+   If <parameter>NO_THROW</parameter> is specified, the command returns
+   a status string instead of throwing errors.
   </para>
 
   <para>
-    The possible return values are <literal>success</literal>,
-    <literal>timeout</literal>, and <literal>not in recovery</literal>.
+   The possible return values are <literal>success</literal>,
+   <literal>timeout</literal>, and <literal>not in recovery</literal>.
   </para>
  </refsect1>
 
@@ -72,6 +85,65 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
       The following parameters are supported:
 
       <variablelist>
+       <varlistentry>
+        <term><literal>MODE</literal> '<replaceable class="parameter">mode</replaceable>'</term>
+        <listitem>
+         <para>
+          Specifies the type of LSN processing to wait for. If not specified,
+          the default is <literal>standby_replay</literal>. The valid modes are:
+         </para>
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>standby_replay</literal>: Wait for the LSN to be replayed
+            (applied to the database) on a standby server. After successful
+            completion, <function>pg_last_wal_replay_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_write</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and written to disk on a
+            standby server, but not yet flushed. This is faster than
+            <literal>standby_flush</literal> but provides weaker durability
+            guarantees since the data may still be in operating system
+            buffers. After successful completion, the
+            <structfield>written_lsn</structfield> column in
+            <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link> will show
+            a value greater than or equal to the target LSN. This mode can
+            only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>standby_flush</literal>: Wait for the WAL containing the
+            LSN to be received from the primary and flushed to disk on a
+            standby server. This provides a durability guarantee without
+            waiting for the WAL to be applied. After successful completion,
+            <function>pg_last_wal_receive_lsn()</function> will return a
+            value greater than or equal to the target LSN. This value is
+            also available as the <structfield>flushed_lsn</structfield>
+            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
+            <structname>pg_stat_wal_receiver</structname></link>. This mode
+            can only be used during recovery.
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>primary_flush</literal>: Wait for the WAL containing the
+            LSN to be flushed to disk on a primary server. After successful
+            completion, <function>pg_current_wal_flush_lsn()</function> will
+            return a value greater than or equal to the target LSN. This mode
+            can only be used on a primary server (not during recovery).
+           </para>
+          </listitem>
+         </itemizedlist>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>TIMEOUT</literal> '<replaceable class="parameter">timeout</replaceable>'</term>
         <listitem>
@@ -135,9 +207,12 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
     <listitem>
      <para>
       This return value denotes that the database server is not in a recovery
-      state.  This might mean either the database server was not in recovery
-      at the moment of receiving the command, or it was promoted before
-      reaching the target <parameter>lsn</parameter>.
+      state. This might mean either the database server was not in recovery
+      at the moment of receiving the command (i.e., executed on a primary),
+      or it was promoted before reaching the target <parameter>lsn</parameter>.
+      In the promotion case, this status indicates a timeline change occurred,
+      and the application should re-evaluate whether the target LSN is still
+      relevant.
      </para>
     </listitem>
    </varlistentry>
@@ -148,25 +223,34 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Notes</title>
 
   <para>
-    <command>WAIT FOR</command> command waits till
-    <parameter>lsn</parameter> to be replayed on standby.
-    That is, after this command execution, the value returned by
-    <function>pg_last_wal_replay_lsn</function> should be greater or equal
-    to the <parameter>lsn</parameter> value.  This is useful to achieve
-    read-your-writes-consistency, while using async replica for reads and
-    primary for writes.  In that case, the <acronym>lsn</acronym> of the last
-    modification should be stored on the client application side or the
-    connection pooler side.
+   <command>WAIT FOR</command> waits until the specified
+   <parameter>lsn</parameter> is reached according to the specified
+   <parameter>mode</parameter>. The <literal>standby_replay</literal> mode
+   waits for the LSN to be replayed (applied to the database), which is
+   useful to achieve read-your-writes consistency while using an async
+   replica for reads and the primary for writes. The
+   <literal>standby_flush</literal> mode waits for the WAL to be flushed
+   to durable storage on the replica, providing a durability guarantee
+   without waiting for replay. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, which is
+   faster than flush but provides weaker durability guarantees. The
+   <literal>primary_flush</literal> mode waits for WAL to be flushed on
+   a primary server. In all cases, the <acronym>LSN</acronym> of the last
+   modification should be stored on the client application side or the
+   connection pooler side.
   </para>
 
   <para>
-    <command>WAIT FOR</command> command should be called on standby.
-    If a user runs <command>WAIT FOR</command> on primary, it
-    will error out unless <parameter>NO_THROW</parameter> is specified in the WITH clause.
-    However, if <command>WAIT FOR</command> is
-    called on primary promoted from standby and <literal>lsn</literal>
-    was already replayed, then the <command>WAIT FOR</command> command just
-    exits immediately.
+   The standby modes (<literal>standby_replay</literal>,
+   <literal>standby_write</literal>, <literal>standby_flush</literal>)
+   can only be used during recovery, and <literal>primary_flush</literal>
+   can only be used on a primary server. Using the wrong mode for the
+   current server state will result in an error. If a standby is promoted
+   while waiting with a standby mode, the command will return
+   <literal>not in recovery</literal> (or throw an error if
+   <literal>NO_THROW</literal> is not specified). Promotion creates a new
+   timeline, and the LSN being waited for may refer to WAL from the old
+   timeline.
   </para>
 
 </refsect1>
@@ -175,21 +259,21 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>' [ WITH ( <replac
   <title>Examples</title>
 
   <para>
-    You can use <command>WAIT FOR</command> command to wait for
-    the <type>pg_lsn</type> value.  For example, an application could update
-    the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
-    changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
-    on primary server to get the <acronym>lsn</acronym> given that
-    <varname>synchronous_commit</varname> could be set to
-    <literal>off</literal>.
+   You can use <command>WAIT FOR</command> command to wait for
+   the <type>pg_lsn</type> value.  For example, an application could update
+   the <literal>movie</literal> table and get the <acronym>lsn</acronym> after
+   changes just made.  This example uses <function>pg_current_wal_insert_lsn</function>
+   on primary server to get the <acronym>lsn</acronym> given that
+   <varname>synchronous_commit</varname> could be set to
+   <literal>off</literal>.
 
    <programlisting>
 postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
 UPDATE 100
 postgres=# SELECT pg_current_wal_insert_lsn();
-pg_current_wal_insert_lsn
---------------------
-0/306EE20
+ pg_current_wal_insert_lsn
+---------------------------
+ 0/306EE20
 (1 row)
 </programlisting>
 
@@ -200,7 +284,7 @@ pg_current_wal_insert_lsn
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20';
  status
---------
+---------
  success
 (1 row)
 postgres=# SELECT * FROM movie WHERE genre = 'Drama';
@@ -211,7 +295,43 @@ postgres=# SELECT * FROM movie WHERE genre = 'Drama';
   </para>
 
   <para>
-    If the target LSN is not reached before the timeout, the error is thrown.
+   Wait for flush (data durable on replica):
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for write with timeout:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Wait for flush on primary:
+
+<programlisting>
+postgres=# WAIT FOR LSN '0/306EE20' WITH (MODE 'primary_flush');
+ status
+---------
+ success
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   If the target LSN is not reached before the timeout, an error is thrown:
 
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '0.1s');
@@ -221,11 +341,12 @@ ERROR:  timed out while waiting for target LSN 0/306EE20 to be replayed; current
 
   <para>
    The same example uses <command>WAIT FOR</command> with
-   <parameter>NO_THROW</parameter> option.
+   <parameter>NO_THROW</parameter> option:
+
 <programlisting>
 postgres=# WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '100ms', NO_THROW);
  status
---------
+---------
  timeout
 (1 row)
 </programlisting>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 05ac7c5f7f8..81dc86847c0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2918,6 +2918,14 @@ XLogFlush(XLogRecPtr record)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * If we still haven't flushed to the request point then we have a
 	 * problem; most likely, the requested flush point is past end of XLOG.
@@ -3100,6 +3108,14 @@ XLogBackgroundFlush(void)
 	/* wake up walsenders now that we've released heavily contended locks */
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
+	/*
+	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogwrtResult.Flush >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
@@ -6277,10 +6293,12 @@ StartupXLOG(void)
 	WakeupCheckpointer();
 
 	/*
-	 * Wake up all waiters for replay LSN.  They need to report an error that
-	 * recovery was ended before reaching the target LSN.
+	 * Wake up all waiters.  They need to report an error that recovery was
+	 * ended before reaching the target LSN.
 	 */
 	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, InvalidXLogRecPtr);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, InvalidXLogRecPtr);
 
 	/*
 	 * Shutdown the recovery environment.  This must occur after
diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 4867f59691e..264f81571d4 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -2,7 +2,7 @@
  *
  * wait.c
  *	  Implements WAIT FOR, which allows waiting for events such as
- *	  time passing or LSN having been replayed on replica.
+ *	  time passing or LSN having been replayed, flushed, or written.
  *
  * Portions Copyright (c) 2025-2026, PostgreSQL Global Development Group
  *
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
 #include "commands/defrem.h"
@@ -34,12 +35,14 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	XLogRecPtr	lsn;
 	int64		timeout = 0;
 	WaitLSNResult waitLSNResult;
+	WaitLSNType lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY; /* default */
 	bool		throw = true;
 	TupleDesc	tupdesc;
 	TupOutputState *tstate;
 	const char *result = "<unset>";
 	bool		timeout_specified = false;
 	bool		no_throw_specified = false;
+	bool		mode_specified = false;
 
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
@@ -47,7 +50,32 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 	foreach_node(DefElem, defel, stmt->options)
 	{
-		if (strcmp(defel->defname, "timeout") == 0)
+		if (strcmp(defel->defname, "mode") == 0)
+		{
+			char	   *mode_str;
+
+			if (mode_specified)
+				errorConflictingDefElem(defel, pstate);
+			mode_specified = true;
+
+			mode_str = defGetString(defel);
+
+			if (pg_strcasecmp(mode_str, "standby_replay") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_REPLAY;
+			else if (pg_strcasecmp(mode_str, "standby_write") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_WRITE;
+			else if (pg_strcasecmp(mode_str, "standby_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_STANDBY_FLUSH;
+			else if (pg_strcasecmp(mode_str, "primary_flush") == 0)
+				lsnType = WAIT_LSN_TYPE_PRIMARY_FLUSH;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized value for %s option \"%s\": \"%s\"",
+								"WAIT", defel->defname, mode_str),
+						 parser_errposition(pstate, defel->location)));
+		}
+		else if (strcmp(defel->defname, "timeout") == 0)
 		{
 			char	   *timeout_str;
 			const char *hintmsg;
@@ -107,8 +135,8 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	}
 
 	/*
-	 * We are going to wait for the LSN replay.  We should first care that we
-	 * don't hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
 	 * Otherwise, our snapshot could prevent the replay of WAL records
 	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
 	 * command, not a procedure or function.
@@ -140,7 +168,22 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	 */
 	Assert(MyProc->xmin == InvalidTransactionId);
 
-	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_STANDBY_REPLAY, lsn, timeout);
+	/*
+	 * Validate that the requested mode matches the current server state.
+	 * Primary modes can only be used on a primary.
+	 */
+	if (lsnType == WAIT_LSN_TYPE_PRIMARY_FLUSH)
+	{
+		if (RecoveryInProgress())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("recovery is in progress"),
+					 errhint("Waiting for primary_flush can only be done on a primary server. "
+							 "Use standby_flush mode on a standby server.")));
+	}
+
+	/* Now wait for the LSN */
+	waitLSNResult = WaitForLSN(lsnType, lsn, timeout);
 
 	/*
 	 * Process the result of WaitForLSN().  Throw appropriate error if needed.
@@ -154,11 +197,48 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 		case WAIT_LSN_RESULT_TIMEOUT:
 			if (throw)
-				ereport(ERROR,
-						errcode(ERRCODE_QUERY_CANCELED),
-						errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
-							   LSN_FORMAT_ARGS(lsn),
-							   LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+			{
+				XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
+				switch (lsnType)
+				{
+					case WAIT_LSN_TYPE_STANDBY_REPLAY:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					case WAIT_LSN_TYPE_STANDBY_WRITE:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be written; current standby_write LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					case WAIT_LSN_TYPE_STANDBY_FLUSH:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be flushed; current standby_flush LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					case WAIT_LSN_TYPE_PRIMARY_FLUSH:
+						ereport(ERROR,
+								errcode(ERRCODE_QUERY_CANCELED),
+								errmsg("timed out while waiting for target LSN %X/%08X to be flushed; current primary_flush LSN %X/%08X",
+									   LSN_FORMAT_ARGS(lsn),
+									   LSN_FORMAT_ARGS(currentLSN)));
+						break;
+
+					default:
+						elog(ERROR, "unexpected wait LSN type %d", lsnType);
+						pg_unreachable();
+				}
+			}
 			else
 				result = "timeout";
 			break;
@@ -168,18 +248,72 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 			{
 				if (PromoteIsTriggered())
 				{
-					ereport(ERROR,
-							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-							errmsg("recovery is not in progress"),
-							errdetail("Recovery ended before replaying target LSN %X/%08X; last replay LSN %X/%08X.",
-									  LSN_FORMAT_ARGS(lsn),
-									  LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
+					XLogRecPtr	currentLSN = GetCurrentLSNForWaitType(lsnType);
+
+					switch (lsnType)
+					{
+						case WAIT_LSN_TYPE_STANDBY_REPLAY:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errdetail("Recovery ended before target LSN %X/%08X was replayed; last standby_replay LSN %X/%08X.",
+											  LSN_FORMAT_ARGS(lsn),
+											  LSN_FORMAT_ARGS(currentLSN)));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_WRITE:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errdetail("Recovery ended before target LSN %X/%08X was written; last standby_write LSN %X/%08X.",
+											  LSN_FORMAT_ARGS(lsn),
+											  LSN_FORMAT_ARGS(currentLSN)));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_FLUSH:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errdetail("Recovery ended before target LSN %X/%08X was flushed; last standby_flush LSN %X/%08X.",
+											  LSN_FORMAT_ARGS(lsn),
+											  LSN_FORMAT_ARGS(currentLSN)));
+							break;
+
+						default:
+							elog(ERROR, "unexpected wait LSN type %d", lsnType);
+							pg_unreachable();
+					}
 				}
 				else
-					ereport(ERROR,
-							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-							errmsg("recovery is not in progress"),
-							errhint("Waiting for the replay LSN can only be executed during recovery."));
+				{
+					switch (lsnType)
+					{
+						case WAIT_LSN_TYPE_STANDBY_REPLAY:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errhint("Waiting for the standby_replay LSN can only be executed during recovery."));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_WRITE:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errhint("Waiting for the standby_write LSN can only be executed during recovery."));
+							break;
+
+						case WAIT_LSN_TYPE_STANDBY_FLUSH:
+							ereport(ERROR,
+									errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+									errmsg("recovery is not in progress"),
+									errhint("Waiting for the standby_flush LSN can only be executed during recovery."));
+							break;
+
+						default:
+							elog(ERROR, "unexpected wait LSN type %d", lsnType);
+							pg_unreachable();
+					}
+				}
 			}
 			else
 				result = "not in recovery";
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ac002f730c3..a41453530a1 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -57,6 +57,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "access/xlogrecovery.h"
+#include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -965,6 +966,14 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	/* Update shared-memory status */
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
+	/*
+	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 */
+	if (waitLSNState &&
+		(LogstreamResult.Write >=
+		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
 	 * the loop, to create its archive notification file soon. Otherwise WAL
@@ -1004,6 +1013,15 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
+		/*
+		 * If we flushed an LSN that someone was waiting for, notify the
+		 * waiters.
+		 */
+		if (waitLSNState &&
+			(LogstreamResult.Flush >=
+			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
 		if (AllowCascadeReplication())
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e0ddb06a2f0..b767b475ff7 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -1,5 +1,6 @@
-# Checks waiting for the LSN replay on standby using
-# the WAIT FOR command.
+# Checks waiting for the LSN using the WAIT FOR command.
+# Tests standby modes (standby_replay/standby_write/standby_flush) on standby
+# and primary_flush mode on primary.
 use strict;
 use warnings FATAL => 'all';
 
@@ -7,6 +8,42 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+# Helper functions to control walreceiver for testing wait conditions.
+# These allow us to stop WAL streaming so waiters block, then resume it.
+my $saved_primary_conninfo;
+
+sub stop_walreceiver
+{
+	my ($node) = @_;
+	$saved_primary_conninfo = $node->safe_psql(
+		'postgres', qq[
+		SELECT pg_catalog.quote_literal(setting)
+		FROM pg_settings
+		WHERE name = 'primary_conninfo';
+	]);
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = '';
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
+sub resume_walreceiver
+{
+	my ($node) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+		ALTER SYSTEM SET primary_conninfo = $saved_primary_conninfo;
+		SELECT pg_reload_conf();
+	]);
+
+	$node->poll_query_until('postgres',
+		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -62,7 +99,52 @@ $output = $node_standby->safe_psql(
 ok((split("\n", $output))[-1] eq 30,
 	"standby reached the same LSN as primary");
 
-# 3. Check that waiting for unreachable LSN triggers the timeout.  The
+# 3. Check that WAIT FOR works with standby_write, standby_flush, and
+# primary_flush modes.
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(31, 40))");
+my $lsn_write =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_write}' WITH (MODE 'standby_write', timeout '1d');
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${lsn_write}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby wrote WAL up to target LSN after WAIT FOR with MODE 'standby_write'"
+);
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(41, 50))");
+my $lsn_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_flush}' WITH (MODE 'standby_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '${lsn_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"standby flushed WAL up to target LSN after WAIT FOR with MODE 'standby_flush'"
+);
+
+# Check primary_flush mode on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(51, 60))");
+my $lsn_primary_flush =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+$output = $node_primary->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${lsn_primary_flush}' WITH (MODE 'primary_flush', timeout '1d');
+	SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '${lsn_primary_flush}'::pg_lsn);
+]);
+
+ok( (split("\n", $output))[-1] >= 0,
+	"primary flushed WAL up to target LSN after WAIT FOR with MODE 'primary_flush'"
+);
+
+# 4. Check that waiting for unreachable LSN triggers the timeout.  The
 # unreachable LSN must be well in advance.  So WAL records issued by
 # the concurrent autovacuum could not affect that.
 my $lsn3 =
@@ -88,14 +170,26 @@ $output = $node_standby->safe_psql(
 	WAIT FOR LSN '${lsn3}' WITH (timeout '10ms', no_throw);]);
 ok($output eq "timeout", "WAIT FOR returns correct status after timeout");
 
-# 4. Check that WAIT FOR triggers an error if called on primary,
-# within another function, or inside a transaction with an isolation level
-# higher than READ COMMITTED.
+# 5. Check mode validation: standby modes error on primary, primary mode errors
+# on standby, and primary_flush works on primary.  Also check that WAIT FOR
+# triggers an error if called within another function or inside a transaction
+# with an isolation level higher than READ COMMITTED.
+
+# Test standby_flush on primary - should error
+$node_primary->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'standby_flush');",
+	stderr => \$stderr);
+ok($stderr =~ /recovery is not in progress/,
+	"get an error when running standby_flush on the primary");
 
-$node_primary->psql('postgres', "WAIT FOR LSN '${lsn3}';",
+# Test primary_flush on standby - should error
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${lsn3}' WITH (MODE 'primary_flush');",
 	stderr => \$stderr);
-ok( $stderr =~ /recovery is not in progress/,
-	"get an error when running on the primary");
+ok($stderr =~ /recovery is in progress/,
+	"get an error when running primary_flush on the standby");
 
 $node_standby->psql(
 	'postgres',
@@ -125,7 +219,7 @@ ok( $stderr =~
 	  /WAIT FOR must be only called without an active or registered snapshot/,
 	"get an error when running within another function");
 
-# 5. Check parameter validation error cases on standby before promotion
+# 6. Check parameter validation error cases on standby before promotion
 my $test_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
 
@@ -208,10 +302,26 @@ $node_standby->psql(
 ok( $stderr =~ /option "invalid_option" not recognized/,
 	"get error for invalid WITH clause option");
 
-# 6. Check the scenario of multiple LSN waiters.  We make 5 background
-# psql sessions each waiting for a corresponding insertion.  When waiting is
-# finished, stored procedures logs if there are visible as many rows as
-# should be.
+# Test invalid MODE value
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'invalid');",
+	stderr => \$stderr);
+ok($stderr =~ /unrecognized value for WAIT option "mode": "invalid"/,
+	"get error for invalid MODE value");
+
+# Test duplicate MODE parameter
+$node_standby->psql(
+	'postgres',
+	"WAIT FOR LSN '${test_lsn}' WITH (MODE 'standby_replay', MODE 'standby_write');",
+	stderr => \$stderr);
+ok( $stderr =~ /conflicting or redundant options/,
+	"get error for duplicate MODE parameter");
+
+# 7a. Check the scenario of multiple standby_replay waiters.  We make 5
+# background psql sessions each waiting for a corresponding insertion.  When
+# waiting is finished, stored procedures logs if there are visible as many
+# rows as should be.
 $node_primary->safe_psql(
 	'postgres', qq[
 CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
@@ -225,8 +335,17 @@ CREATE FUNCTION log_count(i int) RETURNS void AS \$\$
   END
 \$\$
 LANGUAGE plpgsql;
+
+CREATE FUNCTION log_wait_done(prefix text, i int) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '% %', prefix, i;
+  END
+\$\$
+LANGUAGE plpgsql;
 ]);
+
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
 my @psql_sessions;
 for (my $i = 0; $i < 5; $i++)
 {
@@ -243,6 +362,7 @@ for (my $i = 0; $i < 5; $i++)
 		SELECT log_count(${i});
 	]);
 }
+
 my $log_offset = -s $node_standby->logfile;
 $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
 for (my $i = 0; $i < 5; $i++)
@@ -251,23 +371,246 @@ for (my $i = 0; $i < 5; $i++)
 	$psql_sessions[$i]->quit;
 }
 
-ok(1, 'multiple LSN waiters reported consistent data');
+ok(1, 'multiple standby_replay waiters reported consistent data');
+
+# 7b. Check the scenario of multiple standby_write waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @write_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (100 + ${i});");
+	$write_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_write waiters (they will block since walreceiver is stopped)
+my @write_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$write_sessions[$i] = $node_standby->background_psql('postgres');
+	$write_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d');
+		SELECT log_wait_done('write_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalWrite'"
+);
+
+# Restore walreceiver to unblock waiters
+my $write_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("write_done $i", $write_log_offset);
+	$write_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was written up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '$write_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_write waiters: standby wrote WAL up to target LSN");
+
+# 7c. Check the scenario of multiple standby_flush waiters.
+# Stop walreceiver to ensure waiters actually block.
+stop_walreceiver($node_standby);
+
+# Generate WAL on primary (standby won't receive it yet)
+my @flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (200 + ${i});");
+	$flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+# Start standby_flush waiters (they will block since walreceiver is stopped)
+my @flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$flush_sessions[$i] = $node_standby->background_psql('postgres');
+	$flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d');
+		SELECT log_wait_done('flush_done', $i);
+	]);
+}
+
+# Verify waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 5 FROM pg_stat_activity WHERE wait_event = 'WaitForWalFlush'"
+);
+
+# Restore walreceiver to unblock waiters
+my $flush_log_offset = -s $node_standby->logfile;
+resume_walreceiver($node_standby);
+
+# Wait for all waiters to complete and close sessions
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_standby->wait_for_log("flush_done $i", $flush_log_offset);
+	$flush_sessions[$i]->quit;
+}
+
+# Verify on standby that WAL was flushed up to the target LSN
+$output = $node_standby->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_last_wal_receive_lsn(), '$flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple standby_flush waiters: standby flushed WAL up to target LSN");
+
+# 7d. Check the scenario of mixed standby mode waiters (standby_replay,
+# standby_write, standby_flush) running concurrently.  We start 6 sessions:
+# 2 for each mode, all waiting for the same target LSN.  We stop the
+# walreceiver and pause replay to ensure all waiters block.  Then we resume
+# replay and restart the walreceiver to verify they unblock and complete
+# correctly.
+
+# Stop walreceiver first to ensure we can control the flow without hanging
+# (stopping it after pausing replay can hang if the startup process is paused).
+stop_walreceiver($node_standby);
+
+# Pause replay
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause();");
+
+# Generate WAL on primary
+$node_primary->safe_psql('postgres',
+	"INSERT INTO wait_test VALUES (generate_series(301, 310));");
+my $mixed_target_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Start 6 waiters: 2 for each mode
+my @mixed_sessions;
+my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush');
+for (my $i = 0; $i < 6; $i++)
+{
+	$mixed_sessions[$i] = $node_standby->background_psql('postgres');
+	$mixed_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d');
+		SELECT log_wait_done('mixed_done', $i);
+	]);
+}
+
+# Verify all waiters are blocked
+$node_standby->poll_query_until('postgres',
+	"SELECT count(*) = 6 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+);
+
+# Resume replay (waiters should still be blocked as no WAL has arrived)
+my $mixed_log_offset = -s $node_standby->logfile;
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();");
+$node_standby->poll_query_until('postgres',
+	"SELECT NOT pg_is_wal_replay_paused();");
+
+# Restore walreceiver to allow WAL to arrive
+resume_walreceiver($node_standby);
 
-# 7. Check that the standby promotion terminates the wait on LSN.  Start
-# waiting for an unreachable LSN then promote.  Check the log for the relevant
-# error message.  Also, check that waiting for already replayed LSN doesn't
-# cause an error even after promotion.
+# Wait for all sessions to complete and close them
+for (my $i = 0; $i < 6; $i++)
+{
+	$node_standby->wait_for_log("mixed_done $i", $mixed_log_offset);
+	$mixed_sessions[$i]->quit;
+}
+
+# Verify all modes reached the target LSN
+$output = $node_standby->safe_psql(
+	'postgres', qq[
+	SELECT pg_lsn_cmp((SELECT written_lsn FROM pg_stat_wal_receiver), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_receive_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0 AND
+	       pg_lsn_cmp(pg_last_wal_replay_lsn(), '${mixed_target_lsn}'::pg_lsn) >= 0;
+]);
+
+ok($output eq 't',
+	"mixed mode waiters: all modes completed and reached target LSN");
+
+# 7e. Check the scenario of multiple primary_flush waiters on primary.
+# We start 5 background sessions waiting for different LSNs with primary_flush
+# mode.  Each waiter logs when done.
+my @primary_flush_lsns;
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->safe_psql('postgres',
+		"INSERT INTO wait_test VALUES (400 + ${i});");
+	$primary_flush_lsns[$i] =
+	  $node_primary->safe_psql('postgres',
+		"SELECT pg_current_wal_insert_lsn()");
+}
+
+my $primary_flush_log_offset = -s $node_primary->logfile;
+
+# Start primary_flush waiters
+my @primary_flush_sessions;
+for (my $i = 0; $i < 5; $i++)
+{
+	$primary_flush_sessions[$i] = $node_primary->background_psql('postgres');
+	$primary_flush_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d');
+		SELECT log_wait_done('primary_flush_done', $i);
+	]);
+}
+
+# The WAL should already be flushed, so waiters should complete quickly
+for (my $i = 0; $i < 5; $i++)
+{
+	$node_primary->wait_for_log("primary_flush_done $i",
+		$primary_flush_log_offset);
+	$primary_flush_sessions[$i]->quit;
+}
+
+# Verify on primary that WAL was flushed up to the target LSN
+$output = $node_primary->safe_psql('postgres',
+	"SELECT pg_lsn_cmp(pg_current_wal_flush_lsn(), '$primary_flush_lsns[4]'::pg_lsn);"
+);
+
+ok($output >= 0,
+	"multiple primary_flush waiters: primary flushed WAL up to target LSN");
+
+# 8. Check that the standby promotion terminates all standby wait modes.  Start
+# waiting for unreachable LSNs with standby_replay, standby_write, and
+# standby_flush modes, then promote.  Check the log for the relevant error
+# messages.  Also, check that waiting for already replayed LSN doesn't cause
+# an error even after promotion.
 my $lsn4 =
   $node_primary->safe_psql('postgres',
 	"SELECT pg_current_wal_insert_lsn() + 10000000000");
+
 my $lsn5 =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
-my $psql_session = $node_standby->background_psql('postgres');
-$psql_session->query_until(
-	qr/start/, qq[
-	\\echo start
-	WAIT FOR LSN '${lsn4}';
-]);
+
+# Start background sessions waiting for unreachable LSN with all modes
+my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush');
+my @wait_sessions;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i] = $node_standby->background_psql('postgres');
+	$wait_sessions[$i]->query_until(
+		qr/start/, qq[
+		\\echo start
+		WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]');
+	]);
+}
 
 # Make sure standby will be promoted at least at the primary insert LSN we
 # have just observed.  Use pg_switch_wal() to force the insert LSN to be
@@ -277,9 +620,16 @@ $node_primary->wait_for_catchup($node_standby);
 
 $log_offset = -s $node_standby->logfile;
 $node_standby->promote;
-$node_standby->wait_for_log('recovery is not in progress', $log_offset);
 
-ok(1, 'got error after standby promote');
+# Wait for all three sessions to get the error (each mode has distinct message)
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was written/,
+	$log_offset);
+$node_standby->wait_for_log(qr/Recovery ended before target LSN.*was flushed/,
+	$log_offset);
+$node_standby->wait_for_log(
+	qr/Recovery ended before target LSN.*was replayed/, $log_offset);
+
+ok(1, 'promotion interrupted all wait modes');
 
 $node_standby->safe_psql('postgres', "WAIT FOR LSN '${lsn5}';");
 
@@ -295,8 +645,11 @@ ok($output eq "not in recovery",
 $node_standby->stop;
 $node_primary->stop;
 
-# If we send \q with $psql_session->quit the command can be sent to the session
+# If we send \q with $session->quit the command can be sent to the session
 # already closed. So \q is in initial script, here we only finish IPC::Run.
-$psql_session->{run}->finish;
+for (my $i = 0; $i < 3; $i++)
+{
+	$wait_sessions[$i]->{run}->finish;
+}
 
 done_testing();
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-01-03 16:10                                                                                                               ` Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-03 16:10 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Sat, Jan 3, 2026 at 6:54 AM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> On Fri, Jan 2, 2026 at 11:17 AM Xuneng Zhou <[email protected]> wrote:
> > On Fri, Jan 2, 2026 at 7:42 AM Alexander Korotkov <[email protected]> wrote:
> > >
> > > On Thu, Jan 1, 2026 at 7:16 PM Álvaro Herrera <[email protected]> wrote:
> > > > In 0002 you have this kind of thing:
> > > >
> > > > >                               ereport(ERROR,
> > > > >                                               errcode(ERRCODE_QUERY_CANCELED),
> > > > > -                                             errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current replay LSN %X/%08X",
> > > > > +                                             errmsg("timed out while waiting for target LSN %X/%08X to be %s; current %s LSN %X/%08X",
> > > > >                                                          LSN_FORMAT_ARGS(lsn),
> > > > > -                                                        LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL))));
> > > > > +                                                        desc->verb,
> > > > > +                                                        desc->noun,
> > > > > +                                                        LSN_FORMAT_ARGS(currentLSN)));
> > > > > +                     }
> > > >
> > > >
> > > > I'm afraid this technique doesn't work, for translatability reasons.
> > > > Your whole design of having a struct with ->verb and ->noun is not
> > > > workable (which is a pity, but you can't really fight this.) You need to
> > > > spell out the whole messages for each case, something like
> > > >
> > > > if (lsntype == replay)
> > > >    ereport(ERROR,
> > > >            errcode(ERRCODE_QUERY_CANCELED),
> > > >            errmsg("timed out while waiting for target LSN %X/%08X to be replayed; current standby_replay LSN %X/%08X",
> > > > else if (lsntype == flush)
> > > >     ereport( ... )
> > > >
> > > > and so on.  This means four separate messages for translation for each
> > > > message your patch is adding, which is IMO the correct approach.
> > >
> > > +1
> > > Thank you for catching this, Alvaro.  Yes, I think we need to get rid
> > > of WaitLSNTypeDesc.  It's nice idea, but we support too many languages
> > > to have something like this.
> > >
> >
> > Thanks for pointing this out. This approach doesn’t scale to multiple
> > languages. While switch statements are more verbose, the extra clarity
> > is justified to preserve proper internationalization. Please check the
> > updated v12.
>
> I've corrected the patchset.  Mostly changed just comments, formatting
> etc.  I'm going to push it if no objections.
>

Thanks for updating the patchset. LGTM.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-06 05:42                                                                                                                 ` Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Thomas Munro @ 2026-01-06 05:42 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Could this be causing the recent flapping failures on CI/macOS in
recovery/031_recovery_conflict?  I didn't have time to dig personally
but f30848cb looks relevant:

Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
conflict with recovery
DETAIL:  User was or might have been using tablespace that must be dropped.'
while running 'psql --no-psqlrc --no-align --tuples-only --quiet
--dbname port=25195
host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
line 2300.

https://cirrus-ci.com/task/5771274900733952

The master branch in time-descending order, macOS tasks only:

     task_id      | substring |  status
------------------+-----------+-----------
 6460882231754752 | c970bdc0  | FAILED
 5771274900733952 | 6ca8506e  | FAILED
 6217757068361728 | 63ed3bc7  | FAILED
 5980650261446656 | ae283736  | FAILED
 6585898394976256 | 5f13999a  | COMPLETED
 4527474786172928 | 7f9acc9b  | COMPLETED
 4826100842364928 | e8d4e94a  | COMPLETED
 4540563027918848 | b9ee5f2d  | FAILED
 6358528648019968 | c5af141c  | FAILED
 5998005284765696 | e212a0f8  | COMPLETED
 6488580526178304 | b85d5dc0  | FAILED
 5034091344560128 | 7dc95cc3  | ABORTED
 5688692477526016 | bb048e31  | COMPLETED
 5481187977723904 | d351063e  | COMPLETED
 5101831568752640 | f30848cb  | COMPLETED <-- the change
 6395317408497664 | 3f33b63d  | COMPLETED
 6741325208354816 | 877ae5db  | COMPLETED
 4594007789010944 | de746e0d  | COMPLETED
 6497208998035456 | 461b8cc9  | COMPLETED





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
@ 2026-01-06 07:29                                                                                                                   ` Xuneng Zhou <[email protected]>
  2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-06 07:29 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Thomas,

On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
>
> Could this be causing the recent flapping failures on CI/macOS in
> recovery/031_recovery_conflict?  I didn't have time to dig personally
> but f30848cb looks relevant:
>
> Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> conflict with recovery
> DETAIL:  User was or might have been using tablespace that must be dropped.'
> while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> --dbname port=25195
> host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> line 2300.
>
> https://cirrus-ci.com/task/5771274900733952
>
> The master branch in time-descending order, macOS tasks only:
>
>      task_id      | substring |  status
> ------------------+-----------+-----------
>  6460882231754752 | c970bdc0  | FAILED
>  5771274900733952 | 6ca8506e  | FAILED
>  6217757068361728 | 63ed3bc7  | FAILED
>  5980650261446656 | ae283736  | FAILED
>  6585898394976256 | 5f13999a  | COMPLETED
>  4527474786172928 | 7f9acc9b  | COMPLETED
>  4826100842364928 | e8d4e94a  | COMPLETED
>  4540563027918848 | b9ee5f2d  | FAILED
>  6358528648019968 | c5af141c  | FAILED
>  5998005284765696 | e212a0f8  | COMPLETED
>  6488580526178304 | b85d5dc0  | FAILED
>  5034091344560128 | 7dc95cc3  | ABORTED
>  5688692477526016 | bb048e31  | COMPLETED
>  5481187977723904 | d351063e  | COMPLETED
>  5101831568752640 | f30848cb  | COMPLETED <-- the change
>  6395317408497664 | 3f33b63d  | COMPLETED
>  6741325208354816 | 877ae5db  | COMPLETED
>  4594007789010944 | de746e0d  | COMPLETED
>  6497208998035456 | 461b8cc9  | COMPLETED

Thanks for raising this issue. I think it is related to f30848cb after
some analysis. I'll prepare a follow-up patch to fix it.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-06 11:54                                                                                                                     ` Alexander Korotkov <[email protected]>
  2026-01-06 13:12                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-06 11:54 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Jan 6, 2026 at 9:29 AM Xuneng Zhou <[email protected]> wrote:
> On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
> > Could this be causing the recent flapping failures on CI/macOS in
> > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > but f30848cb looks relevant:
> >
> > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > conflict with recovery
> > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > --dbname port=25195
> > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > line 2300.
> >
> > https://cirrus-ci.com/task/5771274900733952
> >
> > The master branch in time-descending order, macOS tasks only:
> >
> >      task_id      | substring |  status
> > ------------------+-----------+-----------
> >  6460882231754752 | c970bdc0  | FAILED
> >  5771274900733952 | 6ca8506e  | FAILED
> >  6217757068361728 | 63ed3bc7  | FAILED
> >  5980650261446656 | ae283736  | FAILED
> >  6585898394976256 | 5f13999a  | COMPLETED
> >  4527474786172928 | 7f9acc9b  | COMPLETED
> >  4826100842364928 | e8d4e94a  | COMPLETED
> >  4540563027918848 | b9ee5f2d  | FAILED
> >  6358528648019968 | c5af141c  | FAILED
> >  5998005284765696 | e212a0f8  | COMPLETED
> >  6488580526178304 | b85d5dc0  | FAILED
> >  5034091344560128 | 7dc95cc3  | ABORTED
> >  5688692477526016 | bb048e31  | COMPLETED
> >  5481187977723904 | d351063e  | COMPLETED
> >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> >  6395317408497664 | 3f33b63d  | COMPLETED
> >  6741325208354816 | 877ae5db  | COMPLETED
> >  4594007789010944 | de746e0d  | COMPLETED
> >  6497208998035456 | 461b8cc9  | COMPLETED
>
> Thanks for raising this issue. I think it is related to f30848cb after
> some analysis. I'll prepare a follow-up patch to fix it.

Sorry, I've mistakenly referenced this report from commit [1].  I
thought it was related, but it appears to be not.  [1] is related to
the report I've got from Ruikai Peng off-list.

Regarding the present failure, could it happen before ExecWaitStmt()
calls PopActiveSnapshot() and InvalidateCatalogSnapshot()?  If so, we
should do preliminary efforts to release these snapshots.

1. https://git.postgresql.org/pg/commitdiff/bf308639bfcfa38541e24733e074184153a8ab7f

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-01-06 13:12                                                                                                                       ` Xuneng Zhou <[email protected]>
  2026-01-06 15:34                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-06 15:53                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-06 13:12 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Jan 6, 2026 at 7:54 PM Alexander Korotkov <[email protected]> wrote:
>
> On Tue, Jan 6, 2026 at 9:29 AM Xuneng Zhou <[email protected]> wrote:
> > On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
> > > Could this be causing the recent flapping failures on CI/macOS in
> > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > but f30848cb looks relevant:
> > >
> > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > conflict with recovery
> > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > --dbname port=25195
> > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > line 2300.
> > >
> > > https://cirrus-ci.com/task/5771274900733952
> > >
> > > The master branch in time-descending order, macOS tasks only:
> > >
> > >      task_id      | substring |  status
> > > ------------------+-----------+-----------
> > >  6460882231754752 | c970bdc0  | FAILED
> > >  5771274900733952 | 6ca8506e  | FAILED
> > >  6217757068361728 | 63ed3bc7  | FAILED
> > >  5980650261446656 | ae283736  | FAILED
> > >  6585898394976256 | 5f13999a  | COMPLETED
> > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > >  4826100842364928 | e8d4e94a  | COMPLETED
> > >  4540563027918848 | b9ee5f2d  | FAILED
> > >  6358528648019968 | c5af141c  | FAILED
> > >  5998005284765696 | e212a0f8  | COMPLETED
> > >  6488580526178304 | b85d5dc0  | FAILED
> > >  5034091344560128 | 7dc95cc3  | ABORTED
> > >  5688692477526016 | bb048e31  | COMPLETED
> > >  5481187977723904 | d351063e  | COMPLETED
> > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > >  6395317408497664 | 3f33b63d  | COMPLETED
> > >  6741325208354816 | 877ae5db  | COMPLETED
> > >  4594007789010944 | de746e0d  | COMPLETED
> > >  6497208998035456 | 461b8cc9  | COMPLETED
> >
> > Thanks for raising this issue. I think it is related to f30848cb after
> > some analysis. I'll prepare a follow-up patch to fix it.
>
> Sorry, I've mistakenly referenced this report from commit [1].  I
> thought it was related, but it appears to be not.  [1] is related to
> the report I've got from Ruikai Peng off-list.
>
> Regarding the present failure, could it happen before ExecWaitStmt()
> calls PopActiveSnapshot() and InvalidateCatalogSnapshot()?  If so, we
> should do preliminary efforts to release these snapshots.
>
> 1. https://git.postgresql.org/pg/commitdiff/bf308639bfcfa38541e24733e074184153a8ab7f
>

I agree that moving PopActiveSnapshot() and
InvalidateCatalogSnapshot() to the very beginning of ExecWaitStmt()
appears to be a sensible optimization. However, in this particular
failure scenario, it may not address the issue.

For tablespace conflicts, recovery conflict resolution uses
GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid), which
returns all active backends, regardless of their snapshot state. As a
result, even if all snapshots are released at the start of
ExecWaitStmt(), the session would still be canceled during replay of
DROP TABLESPACE.

Given this, I am considering handling this conflict class explicitly:
if the WAIT FOR statement is terminated and the error indicates a
recovery conflict, we fall back to the existing polling-based
approach.

* Ask everybody to cancel their queries immediately so we can ensure no
* temp files remain and we can remove the tablespace. Nuke the entire
* site from orbit, it's the only way to be sure.
*
* XXX: We could work out the pids of active backends using this
* tablespace by examining the temp filenames in the directory. We would
* then convert the pids into VirtualXIDs before attempting to cancel
* them.

I am also wondering whether this optimization would be helpful.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Fix-wait_for_catchup-failure-when-standby-session.patch (4.4K, ../../CABPTF7UtCZW4EcOaTDnBgMxmdsx9RS_d5Q+LbfroQYLzK2g__A@mail.gmail.com/2-v1-0001-Fix-wait_for_catchup-failure-when-standby-session.patch)
  download | inline diff:
From a9d8230639118d932883c51cc4b6ecf214840022 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 6 Jan 2026 20:55:43 +0800
Subject: [PATCH v1] Fix wait_for_catchup() failure when standby session is
 killed by recovery conflict

Commit f30848cb optimized wait_for_catchup() to use WAIT FOR LSN on the standby instead of polling pg_stat_replication on the primary. However, this introduced a failure mode: the WAIT FOR LSN session can be killed by recovery conflicts on the standby, causing the test helper to die unexpectedly.

This manifests as flapping failures in tests like 031_recovery_conflict, where DROP TABLESPACE on the primary triggers ResolveRecoveryConflictWithTablespace() on the standby. That function kills all backends indiscriminately, including the innocent WAIT FOR LSN session that happens to be connected at that moment.

Fix by wrapping the WAIT FOR LSN call in an eval block and falling back to the original polling approach when the session is killed by a recovery conflict. The fallback is selective:

- If WAIT FOR LSN succeeds with 'success': return immediately

- If WAIT FOR LSN returns non-success (timeout, not_in_recovery): fail immediately with diagnostics

- If the session is killed by a recovery conflict (error contains "conflict with recovery"): fall back to polling on the primary

- For any other error: fail immediately to avoid masking real problems

The polling fallback is immune to standby-side conflicts because it queries pg_stat_replication on the primary, not the standby.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 53 +++++++++++++++++++-----
 1 file changed, 42 insertions(+), 11 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index a28ea89aa10..08379aeb8fb 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3401,22 +3401,52 @@ sub wait_for_catchup
 			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
 			my $wait_query =
 			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
-			my $output = $standby_node->safe_psql('postgres', $wait_query);
-			chomp($output);
 
-			if ($output ne 'success')
+			# Try WAIT FOR LSN. If it succeeds, we're done. If it returns a
+			# non-success status (timeout, not_in_recovery), fail immediately.
+			# If the session is interrupted (e.g., killed by recovery conflict),
+			# fall back to polling on the upstream which is immune to standby-
+			# side conflicts.
+			my $output;
+			local $@;
+			my $wait_succeeded = eval {
+				$output = $standby_node->safe_psql('postgres', $wait_query);
+				chomp($output);
+				1;
+			};
+
+			if ($wait_succeeded && $output eq 'success')
+			{
+				print "done\n";
+				return;
+			}
+
+			# If WAIT FOR LSN executed but returned non-success (e.g., timeout,
+			# not_in_recovery), fail immediately with diagnostic info. Falling
+			# back to polling would just waste time.
+			if ($wait_succeeded)
 			{
-				# Fetch additional detail for debugging purposes
 				my $details = $self->safe_psql('postgres',
 					"SELECT * FROM pg_catalog.pg_stat_replication");
-				diag qq(WAIT FOR LSN failed with status:
-	${output});
-				diag qq(Last pg_stat_replication contents:
-	${details});
-				croak "failed waiting for catchup";
+				diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+				croak "WAIT FOR LSN '$wait_mode' returned '$output'";
+			}
+
+			# WAIT FOR LSN was interrupted. Only fall back to polling if this
+			# looks like a recovery conflict - the canonical PostgreSQL error
+			# message contains "conflict with recovery". Other errors should
+			# fail immediately rather than being masked by a silent fallback.
+			if ($@ =~ /conflict with recovery/i)
+			{
+				diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+			}
+			else
+			{
+				croak "WAIT FOR LSN failed: $@";
 			}
-			print "done\n";
-			return;
 		}
 	}
 
@@ -3424,6 +3454,7 @@ sub wait_for_catchup
 	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
 	# - When standby_name is a string (e.g., subscription name)
 	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-06 13:12                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-06 15:34                                                                                                                         ` Alexander Korotkov <[email protected]>
  2026-01-06 15:58                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-06 15:34 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Jan 6, 2026 at 3:12 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Tue, Jan 6, 2026 at 7:54 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Tue, Jan 6, 2026 at 9:29 AM Xuneng Zhou <[email protected]> wrote:
> > > On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
> > > > Could this be causing the recent flapping failures on CI/macOS in
> > > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > > but f30848cb looks relevant:
> > > >
> > > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > > conflict with recovery
> > > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > > --dbname port=25195
> > > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > > line 2300.
> > > >
> > > > https://cirrus-ci.com/task/5771274900733952
> > > >
> > > > The master branch in time-descending order, macOS tasks only:
> > > >
> > > >      task_id      | substring |  status
> > > > ------------------+-----------+-----------
> > > >  6460882231754752 | c970bdc0  | FAILED
> > > >  5771274900733952 | 6ca8506e  | FAILED
> > > >  6217757068361728 | 63ed3bc7  | FAILED
> > > >  5980650261446656 | ae283736  | FAILED
> > > >  6585898394976256 | 5f13999a  | COMPLETED
> > > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > > >  4826100842364928 | e8d4e94a  | COMPLETED
> > > >  4540563027918848 | b9ee5f2d  | FAILED
> > > >  6358528648019968 | c5af141c  | FAILED
> > > >  5998005284765696 | e212a0f8  | COMPLETED
> > > >  6488580526178304 | b85d5dc0  | FAILED
> > > >  5034091344560128 | 7dc95cc3  | ABORTED
> > > >  5688692477526016 | bb048e31  | COMPLETED
> > > >  5481187977723904 | d351063e  | COMPLETED
> > > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > > >  6395317408497664 | 3f33b63d  | COMPLETED
> > > >  6741325208354816 | 877ae5db  | COMPLETED
> > > >  4594007789010944 | de746e0d  | COMPLETED
> > > >  6497208998035456 | 461b8cc9  | COMPLETED
> > >
> > > Thanks for raising this issue. I think it is related to f30848cb after
> > > some analysis. I'll prepare a follow-up patch to fix it.
> >
> > Sorry, I've mistakenly referenced this report from commit [1].  I
> > thought it was related, but it appears to be not.  [1] is related to
> > the report I've got from Ruikai Peng off-list.
> >
> > Regarding the present failure, could it happen before ExecWaitStmt()
> > calls PopActiveSnapshot() and InvalidateCatalogSnapshot()?  If so, we
> > should do preliminary efforts to release these snapshots.
> >
> > 1. https://git.postgresql.org/pg/commitdiff/bf308639bfcfa38541e24733e074184153a8ab7f
> >
>
> I agree that moving PopActiveSnapshot() and
> InvalidateCatalogSnapshot() to the very beginning of ExecWaitStmt()
> appears to be a sensible optimization. However, in this particular
> failure scenario, it may not address the issue.
>
> For tablespace conflicts, recovery conflict resolution uses
> GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid), which
> returns all active backends, regardless of their snapshot state. As a
> result, even if all snapshots are released at the start of
> ExecWaitStmt(), the session would still be canceled during replay of
> DROP TABLESPACE.

GetConflictingVirtualXIDs() uses proc->xmin to detect the conflicts.
ExecWaitStmt() asserts MyProc->xmin == InvalidTransactionId after
releasing all the snapshots.  I still think this happens because
conflict handling happens before ExecWaitStmt() manages to release the
snapshots.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-06 13:12                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 15:34                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-01-06 15:58                                                                                                                           ` Xuneng Zhou <[email protected]>
  2026-01-06 17:04                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-06 15:58 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,


On Tue, Jan 6, 2026 at 11:34 PM Alexander Korotkov <[email protected]> wrote:
>
> On Tue, Jan 6, 2026 at 3:12 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Tue, Jan 6, 2026 at 7:54 PM Alexander Korotkov <[email protected]> wrote:
> > >
> > > On Tue, Jan 6, 2026 at 9:29 AM Xuneng Zhou <[email protected]> wrote:
> > > > On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
> > > > > Could this be causing the recent flapping failures on CI/macOS in
> > > > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > > > but f30848cb looks relevant:
> > > > >
> > > > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > > > conflict with recovery
> > > > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > > > --dbname port=25195
> > > > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > > > line 2300.
> > > > >
> > > > > https://cirrus-ci.com/task/5771274900733952
> > > > >
> > > > > The master branch in time-descending order, macOS tasks only:
> > > > >
> > > > >      task_id      | substring |  status
> > > > > ------------------+-----------+-----------
> > > > >  6460882231754752 | c970bdc0  | FAILED
> > > > >  5771274900733952 | 6ca8506e  | FAILED
> > > > >  6217757068361728 | 63ed3bc7  | FAILED
> > > > >  5980650261446656 | ae283736  | FAILED
> > > > >  6585898394976256 | 5f13999a  | COMPLETED
> > > > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > > > >  4826100842364928 | e8d4e94a  | COMPLETED
> > > > >  4540563027918848 | b9ee5f2d  | FAILED
> > > > >  6358528648019968 | c5af141c  | FAILED
> > > > >  5998005284765696 | e212a0f8  | COMPLETED
> > > > >  6488580526178304 | b85d5dc0  | FAILED
> > > > >  5034091344560128 | 7dc95cc3  | ABORTED
> > > > >  5688692477526016 | bb048e31  | COMPLETED
> > > > >  5481187977723904 | d351063e  | COMPLETED
> > > > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > > > >  6395317408497664 | 3f33b63d  | COMPLETED
> > > > >  6741325208354816 | 877ae5db  | COMPLETED
> > > > >  4594007789010944 | de746e0d  | COMPLETED
> > > > >  6497208998035456 | 461b8cc9  | COMPLETED
> > > >
> > > > Thanks for raising this issue. I think it is related to f30848cb after
> > > > some analysis. I'll prepare a follow-up patch to fix it.
> > >
> > > Sorry, I've mistakenly referenced this report from commit [1].  I
> > > thought it was related, but it appears to be not.  [1] is related to
> > > the report I've got from Ruikai Peng off-list.
> > >
> > > Regarding the present failure, could it happen before ExecWaitStmt()
> > > calls PopActiveSnapshot() and InvalidateCatalogSnapshot()?  If so, we
> > > should do preliminary efforts to release these snapshots.
> > >
> > > 1. https://git.postgresql.org/pg/commitdiff/bf308639bfcfa38541e24733e074184153a8ab7f
> > >
> >
> > I agree that moving PopActiveSnapshot() and
> > InvalidateCatalogSnapshot() to the very beginning of ExecWaitStmt()
> > appears to be a sensible optimization. However, in this particular
> > failure scenario, it may not address the issue.
> >
> > For tablespace conflicts, recovery conflict resolution uses
> > GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid), which
> > returns all active backends, regardless of their snapshot state. As a
> > result, even if all snapshots are released at the start of
> > ExecWaitStmt(), the session would still be canceled during replay of
> > DROP TABLESPACE.
>
> GetConflictingVirtualXIDs() uses proc->xmin to detect the conflicts.
> ExecWaitStmt() asserts MyProc->xmin == InvalidTransactionId after
> releasing all the snapshots.  I still think this happens because
> conflict handling happens before ExecWaitStmt() manages to release the
> snapshots.
>

I did not notice this message before. I'll look more closely at this case.

--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-06 13:12                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 15:34                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-06 15:58                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-06 17:04                                                                                                                             ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-06 17:04 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Jan 6, 2026 at 11:58 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
>
> On Tue, Jan 6, 2026 at 11:34 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Tue, Jan 6, 2026 at 3:12 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Tue, Jan 6, 2026 at 7:54 PM Alexander Korotkov <[email protected]> wrote:
> > > >
> > > > On Tue, Jan 6, 2026 at 9:29 AM Xuneng Zhou <[email protected]> wrote:
> > > > > On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
> > > > > > Could this be causing the recent flapping failures on CI/macOS in
> > > > > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > > > > but f30848cb looks relevant:
> > > > > >
> > > > > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > > > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > > > > conflict with recovery
> > > > > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > > > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > > > > --dbname port=25195
> > > > > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > > > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > > > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > > > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > > > > line 2300.
> > > > > >
> > > > > > https://cirrus-ci.com/task/5771274900733952
> > > > > >
> > > > > > The master branch in time-descending order, macOS tasks only:
> > > > > >
> > > > > >      task_id      | substring |  status
> > > > > > ------------------+-----------+-----------
> > > > > >  6460882231754752 | c970bdc0  | FAILED
> > > > > >  5771274900733952 | 6ca8506e  | FAILED
> > > > > >  6217757068361728 | 63ed3bc7  | FAILED
> > > > > >  5980650261446656 | ae283736  | FAILED
> > > > > >  6585898394976256 | 5f13999a  | COMPLETED
> > > > > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > > > > >  4826100842364928 | e8d4e94a  | COMPLETED
> > > > > >  4540563027918848 | b9ee5f2d  | FAILED
> > > > > >  6358528648019968 | c5af141c  | FAILED
> > > > > >  5998005284765696 | e212a0f8  | COMPLETED
> > > > > >  6488580526178304 | b85d5dc0  | FAILED
> > > > > >  5034091344560128 | 7dc95cc3  | ABORTED
> > > > > >  5688692477526016 | bb048e31  | COMPLETED
> > > > > >  5481187977723904 | d351063e  | COMPLETED
> > > > > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > > > > >  6395317408497664 | 3f33b63d  | COMPLETED
> > > > > >  6741325208354816 | 877ae5db  | COMPLETED
> > > > > >  4594007789010944 | de746e0d  | COMPLETED
> > > > > >  6497208998035456 | 461b8cc9  | COMPLETED
> > > > >
> > > > > Thanks for raising this issue. I think it is related to f30848cb after
> > > > > some analysis. I'll prepare a follow-up patch to fix it.
> > > >
> > > > Sorry, I've mistakenly referenced this report from commit [1].  I
> > > > thought it was related, but it appears to be not.  [1] is related to
> > > > the report I've got from Ruikai Peng off-list.
> > > >
> > > > Regarding the present failure, could it happen before ExecWaitStmt()
> > > > calls PopActiveSnapshot() and InvalidateCatalogSnapshot()?  If so, we
> > > > should do preliminary efforts to release these snapshots.
> > > >
> > > > 1. https://git.postgresql.org/pg/commitdiff/bf308639bfcfa38541e24733e074184153a8ab7f
> > > >
> > >
> > > I agree that moving PopActiveSnapshot() and
> > > InvalidateCatalogSnapshot() to the very beginning of ExecWaitStmt()
> > > appears to be a sensible optimization. However, in this particular
> > > failure scenario, it may not address the issue.
> > >
> > > For tablespace conflicts, recovery conflict resolution uses
> > > GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid), which
> > > returns all active backends, regardless of their snapshot state. As a
> > > result, even if all snapshots are released at the start of
> > > ExecWaitStmt(), the session would still be canceled during replay of
> > > DROP TABLESPACE.
> >
> > GetConflictingVirtualXIDs() uses proc->xmin to detect the conflicts.
> > ExecWaitStmt() asserts MyProc->xmin == InvalidTransactionId after
> > releasing all the snapshots.  I still think this happens because
> > conflict handling happens before ExecWaitStmt() manages to release the
> > snapshots.
> >
>
> I did not notice this message before. I'll look more closely at this case.

# VACUUM FREEZE, pruning those dead tuples
$node_primary->safe_psql($test_db, qq[VACUUM FREEZE $table1;]);

# Wait for attempted replay of PRUNE records
$node_primary->wait_for_replay_catchup($node_standby);

check_conflict_log(
"User query might have needed to see row versions that must be removed");
$psql_standby->reconnect_and_clear();
check_conflict_stat("snapshot");

Yeah, this code path could be problematic for the conflict type
PROCSIG_RECOVERY_CONFLICT_SNAPSHOT.  I created a patch to reduce the
false conflict detecting window as you suggested. Please check it too.


--
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Move-snapshot-release-to-the-beginning-of-ExecWai.patch (4.5K, ../../CABPTF7X39KknhC+xMbJgaJ1ydS9Ly5hWYF-Z5WtkcQuyPMNw-A@mail.gmail.com/2-v1-0001-Move-snapshot-release-to-the-beginning-of-ExecWai.patch)
  download | inline diff:
From fef38ad31b4bd0c4ac968a93420a3bb4513e6b15 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 7 Jan 2026 00:57:40 +0800
Subject: [PATCH v1] Move snapshot release to the beginning of ExecWaitStmt()

Move the snapshot handling code (PopActiveSnapshot, InvalidateCatalogSnapshot,
and the HaveRegisteredOrActiveSnapshot check) from after option parsing to
the very beginning of ExecWaitStmt().  This reduces the window during which
the WAIT FOR LSN session could be killed by snapshot-based recovery conflicts.

When a snapshot-based recovery conflict is processed on a hot standby,
GetConflictingVirtualXIDs() targets backends whose xmin <= limitXmin.
By releasing our snapshot and clearing xmin before option parsing, we
become immune to such conflicts during that phase.

This is a pure code movement with no functional change to the snapshot
handling logic itself.  All original comments are preserved.
---
 src/backend/commands/wait.c | 68 ++++++++++++++++++-------------------
 1 file changed, 34 insertions(+), 34 deletions(-)

diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 97f1e778488..dd1daa89623 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -44,6 +44,40 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	bool		no_throw_specified = false;
 	bool		mode_specified = false;
 
+	/*
+	 * We are going to wait for the LSN.  We should first care that we don't
+	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
+	 * Otherwise, our snapshot could prevent the replay of WAL records
+	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
+	 * command, not a procedure or function.
+	 *
+	 * At first, we should check there is no active snapshot.  According to
+	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
+	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
+	 * because PortalRunUtility() can tolerate this.
+	 */
+	if (ActiveSnapshotSet())
+		PopActiveSnapshot();
+
+	/*
+	 * At second, invalidate a catalog snapshot if any.  And we should be done
+	 * with the preparation.
+	 */
+	InvalidateCatalogSnapshot();
+
+	/* Give up if there is still an active or registered snapshot. */
+	if (HaveRegisteredOrActiveSnapshot())
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("WAIT FOR must be called without an active or registered snapshot"),
+				errdetail("WAIT FOR cannot be executed from a function or procedure, nor within a transaction with an isolation level higher than READ COMMITTED."));
+
+	/*
+	 * As the result we should hold no snapshot, and correspondingly our xmin
+	 * should be unset.
+	 */
+	Assert(MyProc->xmin == InvalidTransactionId);
+
 	/* Parse and validate the mandatory LSN */
 	lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in,
 										  CStringGetDatum(stmt->lsn_literal)));
@@ -134,40 +168,6 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 		}
 	}
 
-	/*
-	 * We are going to wait for the LSN.  We should first care that we don't
-	 * hold a snapshot and correspondingly our MyProc->xmin is invalid.
-	 * Otherwise, our snapshot could prevent the replay of WAL records
-	 * implying a kind of self-deadlock.  This is the reason why WAIT FOR is a
-	 * command, not a procedure or function.
-	 *
-	 * At first, we should check there is no active snapshot.  According to
-	 * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is
-	 * processed with a snapshot.  Thankfully, we can pop this snapshot,
-	 * because PortalRunUtility() can tolerate this.
-	 */
-	if (ActiveSnapshotSet())
-		PopActiveSnapshot();
-
-	/*
-	 * At second, invalidate a catalog snapshot if any.  And we should be done
-	 * with the preparation.
-	 */
-	InvalidateCatalogSnapshot();
-
-	/* Give up if there is still an active or registered snapshot. */
-	if (HaveRegisteredOrActiveSnapshot())
-		ereport(ERROR,
-				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				errmsg("WAIT FOR must be called without an active or registered snapshot"),
-				errdetail("WAIT FOR cannot be executed from a function or procedure, nor within a transaction with an isolation level higher than READ COMMITTED."));
-
-	/*
-	 * As the result we should hold no snapshot, and correspondingly our xmin
-	 * should be unset.
-	 */
-	Assert(MyProc->xmin == InvalidTransactionId);
-
 	/*
 	 * Validate that the requested mode matches the current server state.
 	 * Primary modes can only be used on a primary.
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-06 13:12                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-06 15:53                                                                                                                         ` Xuneng Zhou <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-06 15:53 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, Jan 6, 2026 at 9:12 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Tue, Jan 6, 2026 at 7:54 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Tue, Jan 6, 2026 at 9:29 AM Xuneng Zhou <[email protected]> wrote:
> > > On Tue, Jan 6, 2026 at 1:43 PM Thomas Munro <[email protected]> wrote:
> > > > Could this be causing the recent flapping failures on CI/macOS in
> > > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > > but f30848cb looks relevant:
> > > >
> > > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > > conflict with recovery
> > > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > > --dbname port=25195
> > > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > > line 2300.
> > > >
> > > > https://cirrus-ci.com/task/5771274900733952
> > > >
> > > > The master branch in time-descending order, macOS tasks only:
> > > >
> > > >      task_id      | substring |  status
> > > > ------------------+-----------+-----------
> > > >  6460882231754752 | c970bdc0  | FAILED
> > > >  5771274900733952 | 6ca8506e  | FAILED
> > > >  6217757068361728 | 63ed3bc7  | FAILED
> > > >  5980650261446656 | ae283736  | FAILED
> > > >  6585898394976256 | 5f13999a  | COMPLETED
> > > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > > >  4826100842364928 | e8d4e94a  | COMPLETED
> > > >  4540563027918848 | b9ee5f2d  | FAILED
> > > >  6358528648019968 | c5af141c  | FAILED
> > > >  5998005284765696 | e212a0f8  | COMPLETED
> > > >  6488580526178304 | b85d5dc0  | FAILED
> > > >  5034091344560128 | 7dc95cc3  | ABORTED
> > > >  5688692477526016 | bb048e31  | COMPLETED
> > > >  5481187977723904 | d351063e  | COMPLETED
> > > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > > >  6395317408497664 | 3f33b63d  | COMPLETED
> > > >  6741325208354816 | 877ae5db  | COMPLETED
> > > >  4594007789010944 | de746e0d  | COMPLETED
> > > >  6497208998035456 | 461b8cc9  | COMPLETED
> > >
> > > Thanks for raising this issue. I think it is related to f30848cb after
> > > some analysis. I'll prepare a follow-up patch to fix it.
> >
> > Sorry, I've mistakenly referenced this report from commit [1].  I
> > thought it was related, but it appears to be not.  [1] is related to
> > the report I've got from Ruikai Peng off-list.
> >
> > Regarding the present failure, could it happen before ExecWaitStmt()
> > calls PopActiveSnapshot() and InvalidateCatalogSnapshot()?  If so, we
> > should do preliminary efforts to release these snapshots.
> >
> > 1. https://git.postgresql.org/pg/commitdiff/bf308639bfcfa38541e24733e074184153a8ab7f
> >
>
> I agree that moving PopActiveSnapshot() and
> InvalidateCatalogSnapshot() to the very beginning of ExecWaitStmt()
> appears to be a sensible optimization. However, in this particular
> failure scenario, it may not address the issue.
>
> For tablespace conflicts, recovery conflict resolution uses
> GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid), which
> returns all active backends, regardless of their snapshot state. As a
> result, even if all snapshots are released at the start of
> ExecWaitStmt(), the session would still be canceled during replay of
> DROP TABLESPACE.
>
> Given this, I am considering handling this conflict class explicitly:
> if the WAIT FOR statement is terminated and the error indicates a
> recovery conflict, we fall back to the existing polling-based
> approach.
>
> * Ask everybody to cancel their queries immediately so we can ensure no
> * temp files remain and we can remove the tablespace. Nuke the entire
> * site from orbit, it's the only way to be sure.
> *
> * XXX: We could work out the pids of active backends using this
> * tablespace by examining the temp filenames in the directory. We would
> * then convert the pids into VirtualXIDs before attempting to cancel
> * them.
>
> I am also wondering whether this optimization would be helpful.
>

Just format the commit message.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v2-0001-Fix-wait_for_catchup-failure-when-standby-session.patch (4.4K, ../../CABPTF7Wao7Us+-Nw5Vn=Kf==6mG4XEETqMw7k00=9k8=T4kmkw@mail.gmail.com/2-v2-0001-Fix-wait_for_catchup-failure-when-standby-session.patch)
  download | inline diff:
From 1eaf36cbfafb75c91734615529dcc8f0ed7d7999 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 6 Jan 2026 20:55:43 +0800
Subject: [PATCH v2] Fix wait_for_catchup() failure when standby session is
 killed by recovery conflict

Commit f30848cb optimized wait_for_catchup() to use WAIT FOR LSN on
the standby instead of polling pg_stat_replication on the primary.
However, this introduced a failure mode: the WAIT FOR LSN session
can be killed by recovery conflicts on the standby, causing the
test helper to die unexpectedly.

This manifests as flapping failures in tests like 031_recovery_conflict,
where DROP TABLESPACE on the primary triggers
ResolveRecoveryConflictWithTablespace() on the standby. That function
kills all backends indiscriminately, including the innocent WAIT FOR
LSN session that happens to be connected at that moment.

Fix by wrapping the WAIT FOR LSN call in an eval block and falling
back to the original polling approach when the session is killed by
a recovery conflict. The fallback is selective:

- If WAIT FOR LSN succeeds with 'success': return immediately
- If WAIT FOR LSN returns non-success (timeout, not_in_recovery):
  fail immediately with diagnostics
- If the session is killed by a recovery conflict (error contains
  "conflict with recovery"): fall back to polling on the primary
- For any other error: fail immediately to avoid masking real problems

The polling fallback is immune to standby-side conflicts because it
queries pg_stat_replication on the primary, not the standby.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 53 +++++++++++++++++++-----
 1 file changed, 42 insertions(+), 11 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index a28ea89aa10..08379aeb8fb 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3401,22 +3401,52 @@ sub wait_for_catchup
 			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
 			my $wait_query =
 			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
-			my $output = $standby_node->safe_psql('postgres', $wait_query);
-			chomp($output);
 
-			if ($output ne 'success')
+			# Try WAIT FOR LSN. If it succeeds, we're done. If it returns a
+			# non-success status (timeout, not_in_recovery), fail immediately.
+			# If the session is interrupted (e.g., killed by recovery conflict),
+			# fall back to polling on the upstream which is immune to standby-
+			# side conflicts.
+			my $output;
+			local $@;
+			my $wait_succeeded = eval {
+				$output = $standby_node->safe_psql('postgres', $wait_query);
+				chomp($output);
+				1;
+			};
+
+			if ($wait_succeeded && $output eq 'success')
+			{
+				print "done\n";
+				return;
+			}
+
+			# If WAIT FOR LSN executed but returned non-success (e.g., timeout,
+			# not_in_recovery), fail immediately with diagnostic info. Falling
+			# back to polling would just waste time.
+			if ($wait_succeeded)
 			{
-				# Fetch additional detail for debugging purposes
 				my $details = $self->safe_psql('postgres',
 					"SELECT * FROM pg_catalog.pg_stat_replication");
-				diag qq(WAIT FOR LSN failed with status:
-	${output});
-				diag qq(Last pg_stat_replication contents:
-	${details});
-				croak "failed waiting for catchup";
+				diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+				croak "WAIT FOR LSN '$wait_mode' returned '$output'";
+			}
+
+			# WAIT FOR LSN was interrupted. Only fall back to polling if this
+			# looks like a recovery conflict - the canonical PostgreSQL error
+			# message contains "conflict with recovery". Other errors should
+			# fail immediately rather than being masked by a silent fallback.
+			if ($@ =~ /conflict with recovery/i)
+			{
+				diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+			}
+			else
+			{
+				croak "WAIT FOR LSN failed: $@";
 			}
-			print "done\n";
-			return;
 		}
 	}
 
@@ -3424,6 +3454,7 @@ sub wait_for_catchup
 	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
 	# - When standby_name is a string (e.g., subscription name)
 	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
@ 2026-01-07 00:32                                                                                                                   ` Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-07 08:06                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 2 replies; 527+ messages in thread

From: Andres Freund @ 2026-01-07 00:32 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Alexander Korotkov <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2026-01-06 18:42:59 +1300, Thomas Munro wrote:
> Could this be causing the recent flapping failures on CI/macOS in
> recovery/031_recovery_conflict?  I didn't have time to dig personally
> but f30848cb looks relevant:
> 
> Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> conflict with recovery
> DETAIL:  User was or might have been using tablespace that must be dropped.'
> while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> --dbname port=25195
> host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> line 2300.
> 
> https://cirrus-ci.com/task/5771274900733952
> 
> The master branch in time-descending order, macOS tasks only:
> 
>      task_id      | substring |  status
> ------------------+-----------+-----------
>  6460882231754752 | c970bdc0  | FAILED
>  5771274900733952 | 6ca8506e  | FAILED
>  6217757068361728 | 63ed3bc7  | FAILED
>  5980650261446656 | ae283736  | FAILED
>  6585898394976256 | 5f13999a  | COMPLETED
>  4527474786172928 | 7f9acc9b  | COMPLETED
>  4826100842364928 | e8d4e94a  | COMPLETED
>  4540563027918848 | b9ee5f2d  | FAILED
>  6358528648019968 | c5af141c  | FAILED
>  5998005284765696 | e212a0f8  | COMPLETED
>  6488580526178304 | b85d5dc0  | FAILED
>  5034091344560128 | 7dc95cc3  | ABORTED
>  5688692477526016 | bb048e31  | COMPLETED
>  5481187977723904 | d351063e  | COMPLETED
>  5101831568752640 | f30848cb  | COMPLETED <-- the change
>  6395317408497664 | 3f33b63d  | COMPLETED
>  6741325208354816 | 877ae5db  | COMPLETED
>  4594007789010944 | de746e0d  | COMPLETED
>  6497208998035456 | 461b8cc9  | COMPLETED

The failure rates of this are very high - the majority of the CI runs on the
postgres/postgres repos failed since the change went in. Which then also means
cfbot has a very high spurious failure rate. I think we need to revert this
change until the problem has been verified as fixed.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-01-07 04:08                                                                                                                     ` Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-07 04:08 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Alexander Korotkov <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Wed, Jan 7, 2026 at 8:32 AM Andres Freund <[email protected]> wrote:
>
> Hi,
>
> On 2026-01-06 18:42:59 +1300, Thomas Munro wrote:
> > Could this be causing the recent flapping failures on CI/macOS in
> > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > but f30848cb looks relevant:
> >
> > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > conflict with recovery
> > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > --dbname port=25195
> > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > line 2300.
> >
> > https://cirrus-ci.com/task/5771274900733952
> >
> > The master branch in time-descending order, macOS tasks only:
> >
> >      task_id      | substring |  status
> > ------------------+-----------+-----------
> >  6460882231754752 | c970bdc0  | FAILED
> >  5771274900733952 | 6ca8506e  | FAILED
> >  6217757068361728 | 63ed3bc7  | FAILED
> >  5980650261446656 | ae283736  | FAILED
> >  6585898394976256 | 5f13999a  | COMPLETED
> >  4527474786172928 | 7f9acc9b  | COMPLETED
> >  4826100842364928 | e8d4e94a  | COMPLETED
> >  4540563027918848 | b9ee5f2d  | FAILED
> >  6358528648019968 | c5af141c  | FAILED
> >  5998005284765696 | e212a0f8  | COMPLETED
> >  6488580526178304 | b85d5dc0  | FAILED
> >  5034091344560128 | 7dc95cc3  | ABORTED
> >  5688692477526016 | bb048e31  | COMPLETED
> >  5481187977723904 | d351063e  | COMPLETED
> >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> >  6395317408497664 | 3f33b63d  | COMPLETED
> >  6741325208354816 | 877ae5db  | COMPLETED
> >  4594007789010944 | de746e0d  | COMPLETED
> >  6497208998035456 | 461b8cc9  | COMPLETED
>
> The failure rates of this are very high - the majority of the CI runs on the
> postgres/postgres repos failed since the change went in. Which then also means
> cfbot has a very high spurious failure rate. I think we need to revert this
> change until the problem has been verified as fixed.

This specific failure can be reproduced with this patch v1.

I guess the potential race condition is: when
wait_for_replay_catchup() runs WAIT FOR LSN on the standby, if a
tablespace conflict fires during that wait, the WAIT FOR LSN session
is killed even though it doesn't use the tablespace.

In my test, the failure won't occur after applying the v2 patch.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-reproduce-the-failure-in-031_recovery_conflict.pl.patch (1.7K, ../../CABPTF7WN_3kDPBYPxaKKcp2kO5BLB5bK_YGz70VTzTCivHibZA@mail.gmail.com/2-v1-0001-reproduce-the-failure-in-031_recovery_conflict.pl.patch)
  download | inline diff:
From ca73929687f9bf7d4aaa258f8e413ff2c3eea6aa Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 7 Jan 2026 11:39:41 +0800
Subject: [PATCH v1] reproduce the failure in 031_recovery_conflict.pl

---
 src/test/recovery/t/031_recovery_conflict.pl | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/test/recovery/t/031_recovery_conflict.pl b/src/test/recovery/t/031_recovery_conflict.pl
index 7a740f69806..39061fcc0a8 100644
--- a/src/test/recovery/t/031_recovery_conflict.pl
+++ b/src/test/recovery/t/031_recovery_conflict.pl
@@ -198,10 +198,25 @@ like($res, qr/^6000$/m,
 	"$sect: cursor with conflicting temp file established");
 
 # Drop the tablespace currently containing spill files for the query on the
-# standby
+# standby.  We pause replay before the DROP, then resume it via a background
+# session.  This forces wait_for_replay_catchup's internal WAIT FOR LSN to be
+# running when the conflict fires, exercising the recovery conflict handling
+# in Cluster.pm.
+$node_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
 $node_primary->safe_psql($test_db, qq[DROP TABLESPACE $tablespace1;]);
 
+# Start a background session that waits 1 second then resumes replay.
+# This triggers the conflict while wait_for_replay_catchup is running.
+my $resume_session = $node_standby->background_psql('postgres');
+$resume_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	SELECT pg_sleep(1);
+	SELECT pg_wal_replay_resume();
+]);
+
 $node_primary->wait_for_replay_catchup($node_standby);
+$resume_session->quit;
 
 check_conflict_log(
 	"User was or might have been using tablespace that must be dropped");
-- 
2.51.0



  [application/octet-stream] v2-0001-Fix-wait_for_catchup-failure-when-standby-session.patch (4.4K, ../../CABPTF7WN_3kDPBYPxaKKcp2kO5BLB5bK_YGz70VTzTCivHibZA@mail.gmail.com/3-v2-0001-Fix-wait_for_catchup-failure-when-standby-session.patch)
  download | inline diff:
From 1eaf36cbfafb75c91734615529dcc8f0ed7d7999 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 6 Jan 2026 20:55:43 +0800
Subject: [PATCH v2] Fix wait_for_catchup() failure when standby session is
 killed by recovery conflict

Commit f30848cb optimized wait_for_catchup() to use WAIT FOR LSN on
the standby instead of polling pg_stat_replication on the primary.
However, this introduced a failure mode: the WAIT FOR LSN session
can be killed by recovery conflicts on the standby, causing the
test helper to die unexpectedly.

This manifests as flapping failures in tests like 031_recovery_conflict,
where DROP TABLESPACE on the primary triggers
ResolveRecoveryConflictWithTablespace() on the standby. That function
kills all backends indiscriminately, including the innocent WAIT FOR
LSN session that happens to be connected at that moment.

Fix by wrapping the WAIT FOR LSN call in an eval block and falling
back to the original polling approach when the session is killed by
a recovery conflict. The fallback is selective:

- If WAIT FOR LSN succeeds with 'success': return immediately
- If WAIT FOR LSN returns non-success (timeout, not_in_recovery):
  fail immediately with diagnostics
- If the session is killed by a recovery conflict (error contains
  "conflict with recovery"): fall back to polling on the primary
- For any other error: fail immediately to avoid masking real problems

The polling fallback is immune to standby-side conflicts because it
queries pg_stat_replication on the primary, not the standby.
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 53 +++++++++++++++++++-----
 1 file changed, 42 insertions(+), 11 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index a28ea89aa10..08379aeb8fb 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3401,22 +3401,52 @@ sub wait_for_catchup
 			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
 			my $wait_query =
 			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
-			my $output = $standby_node->safe_psql('postgres', $wait_query);
-			chomp($output);
 
-			if ($output ne 'success')
+			# Try WAIT FOR LSN. If it succeeds, we're done. If it returns a
+			# non-success status (timeout, not_in_recovery), fail immediately.
+			# If the session is interrupted (e.g., killed by recovery conflict),
+			# fall back to polling on the upstream which is immune to standby-
+			# side conflicts.
+			my $output;
+			local $@;
+			my $wait_succeeded = eval {
+				$output = $standby_node->safe_psql('postgres', $wait_query);
+				chomp($output);
+				1;
+			};
+
+			if ($wait_succeeded && $output eq 'success')
+			{
+				print "done\n";
+				return;
+			}
+
+			# If WAIT FOR LSN executed but returned non-success (e.g., timeout,
+			# not_in_recovery), fail immediately with diagnostic info. Falling
+			# back to polling would just waste time.
+			if ($wait_succeeded)
 			{
-				# Fetch additional detail for debugging purposes
 				my $details = $self->safe_psql('postgres',
 					"SELECT * FROM pg_catalog.pg_stat_replication");
-				diag qq(WAIT FOR LSN failed with status:
-	${output});
-				diag qq(Last pg_stat_replication contents:
-	${details});
-				croak "failed waiting for catchup";
+				diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+				croak "WAIT FOR LSN '$wait_mode' returned '$output'";
+			}
+
+			# WAIT FOR LSN was interrupted. Only fall back to polling if this
+			# looks like a recovery conflict - the canonical PostgreSQL error
+			# message contains "conflict with recovery". Other errors should
+			# fail immediately rather than being masked by a silent fallback.
+			if ($@ =~ /conflict with recovery/i)
+			{
+				diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+			}
+			else
+			{
+				croak "WAIT FOR LSN failed: $@";
 			}
-			print "done\n";
-			return;
 		}
 	}
 
@@ -3424,6 +3454,7 @@ sub wait_for_catchup
 	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
 	# - When standby_name is a string (e.g., subscription name)
 	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-08 14:19                                                                                                                       ` Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-08 14:19 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Jan 7, 2026 at 6:08 AM Xuneng Zhou <[email protected]> wrote:
> On Wed, Jan 7, 2026 at 8:32 AM Andres Freund <[email protected]> wrote:
> > On 2026-01-06 18:42:59 +1300, Thomas Munro wrote:
> > > Could this be causing the recent flapping failures on CI/macOS in
> > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > but f30848cb looks relevant:
> > >
> > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > conflict with recovery
> > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > --dbname port=25195
> > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > line 2300.
> > >
> > > https://cirrus-ci.com/task/5771274900733952
> > >
> > > The master branch in time-descending order, macOS tasks only:
> > >
> > >      task_id      | substring |  status
> > > ------------------+-----------+-----------
> > >  6460882231754752 | c970bdc0  | FAILED
> > >  5771274900733952 | 6ca8506e  | FAILED
> > >  6217757068361728 | 63ed3bc7  | FAILED
> > >  5980650261446656 | ae283736  | FAILED
> > >  6585898394976256 | 5f13999a  | COMPLETED
> > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > >  4826100842364928 | e8d4e94a  | COMPLETED
> > >  4540563027918848 | b9ee5f2d  | FAILED
> > >  6358528648019968 | c5af141c  | FAILED
> > >  5998005284765696 | e212a0f8  | COMPLETED
> > >  6488580526178304 | b85d5dc0  | FAILED
> > >  5034091344560128 | 7dc95cc3  | ABORTED
> > >  5688692477526016 | bb048e31  | COMPLETED
> > >  5481187977723904 | d351063e  | COMPLETED
> > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > >  6395317408497664 | 3f33b63d  | COMPLETED
> > >  6741325208354816 | 877ae5db  | COMPLETED
> > >  4594007789010944 | de746e0d  | COMPLETED
> > >  6497208998035456 | 461b8cc9  | COMPLETED
> >
> > The failure rates of this are very high - the majority of the CI runs on the
> > postgres/postgres repos failed since the change went in. Which then also means
> > cfbot has a very high spurious failure rate. I think we need to revert this
> > change until the problem has been verified as fixed.
>
> This specific failure can be reproduced with this patch v1.
>
> I guess the potential race condition is: when
> wait_for_replay_catchup() runs WAIT FOR LSN on the standby, if a
> tablespace conflict fires during that wait, the WAIT FOR LSN session
> is killed even though it doesn't use the tablespace.
>
> In my test, the failure won't occur after applying the v2 patch.

I see, you were right.  This is not related to the MyProc->xmin.
ResolveRecoveryConflictWithTablespace() calls
GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid).  That
would kill WAIT FOR LSN query independently on its xmin.  I guess your
patch is the only way to go.  It's clumsy to wrap WAIT FOR LSN call
with retry loop, but it would still consume less resources than
polling.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-01-08 16:29                                                                                                                         ` Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-08 16:29 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Thu, Jan 8, 2026 at 10:19 PM Alexander Korotkov <[email protected]> wrote:
>
> On Wed, Jan 7, 2026 at 6:08 AM Xuneng Zhou <[email protected]> wrote:
> > On Wed, Jan 7, 2026 at 8:32 AM Andres Freund <[email protected]> wrote:
> > > On 2026-01-06 18:42:59 +1300, Thomas Munro wrote:
> > > > Could this be causing the recent flapping failures on CI/macOS in
> > > > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > > > but f30848cb looks relevant:
> > > >
> > > > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on primary
> > > > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > > > conflict with recovery
> > > > DETAIL:  User was or might have been using tablespace that must be dropped.'
> > > > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > > > --dbname port=25195
> > > > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > > > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > > > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > > > no_throw);' at /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > > > line 2300.
> > > >
> > > > https://cirrus-ci.com/task/5771274900733952
> > > >
> > > > The master branch in time-descending order, macOS tasks only:
> > > >
> > > >      task_id      | substring |  status
> > > > ------------------+-----------+-----------
> > > >  6460882231754752 | c970bdc0  | FAILED
> > > >  5771274900733952 | 6ca8506e  | FAILED
> > > >  6217757068361728 | 63ed3bc7  | FAILED
> > > >  5980650261446656 | ae283736  | FAILED
> > > >  6585898394976256 | 5f13999a  | COMPLETED
> > > >  4527474786172928 | 7f9acc9b  | COMPLETED
> > > >  4826100842364928 | e8d4e94a  | COMPLETED
> > > >  4540563027918848 | b9ee5f2d  | FAILED
> > > >  6358528648019968 | c5af141c  | FAILED
> > > >  5998005284765696 | e212a0f8  | COMPLETED
> > > >  6488580526178304 | b85d5dc0  | FAILED
> > > >  5034091344560128 | 7dc95cc3  | ABORTED
> > > >  5688692477526016 | bb048e31  | COMPLETED
> > > >  5481187977723904 | d351063e  | COMPLETED
> > > >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> > > >  6395317408497664 | 3f33b63d  | COMPLETED
> > > >  6741325208354816 | 877ae5db  | COMPLETED
> > > >  4594007789010944 | de746e0d  | COMPLETED
> > > >  6497208998035456 | 461b8cc9  | COMPLETED
> > >
> > > The failure rates of this are very high - the majority of the CI runs on the
> > > postgres/postgres repos failed since the change went in. Which then also means
> > > cfbot has a very high spurious failure rate. I think we need to revert this
> > > change until the problem has been verified as fixed.
> >
> > This specific failure can be reproduced with this patch v1.
> >
> > I guess the potential race condition is: when
> > wait_for_replay_catchup() runs WAIT FOR LSN on the standby, if a
> > tablespace conflict fires during that wait, the WAIT FOR LSN session
> > is killed even though it doesn't use the tablespace.
> >
> > In my test, the failure won't occur after applying the v2 patch.
>
> I see, you were right.  This is not related to the MyProc->xmin.
> ResolveRecoveryConflictWithTablespace() calls
> GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid).  That
> would kill WAIT FOR LSN query independently on its xmin.

I think the concern is valid --- conflicts like
PROCSIG_RECOVERY_CONFLICT_SNAPSHOT could occur and terminate the
backend if the timing is unlucky. It's more difficult to reproduce
though. A check for the log containing "conflict with recovery" would
likely catch these conflicts as well.

> I guess your
> patch is the only way to go.  It's clumsy to wrap WAIT FOR LSN call
> with retry loop, but it would still consume less resources than
> polling.
>

Assuming recovery conflicts are relatively rare in tap tests, except
for the explicitly designed tests like 031_recovery_conflict and the
narrow timing window that the standby has not caught up while the wait
for gets invoked, a simple fallback seems appropriate to me.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-08 20:42                                                                                                                           ` Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-08 20:42 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Jan 8, 2026 at 6:29 PM Xuneng Zhou <[email protected]> wrote:
> On Thu, Jan 8, 2026 at 10:19 PM Alexander Korotkov <[email protected]> wrote:
> > I see, you were right.  This is not related to the MyProc->xmin.
> > ResolveRecoveryConflictWithTablespace() calls
> > GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid).  That
> > would kill WAIT FOR LSN query independently on its xmin.
>
> I think the concern is valid --- conflicts like
> PROCSIG_RECOVERY_CONFLICT_SNAPSHOT could occur and terminate the
> backend if the timing is unlucky. It's more difficult to reproduce
> though. A check for the log containing "conflict with recovery" would
> likely catch these conflicts as well.

Yes, I found multiple reasons why xmin gets temporarily set during
processing of WAIT FOR LSN query.  I'll soon post a draft patch to fix
that.

> > I guess your
> > patch is the only way to go.  It's clumsy to wrap WAIT FOR LSN call
> > with retry loop, but it would still consume less resources than
> > polling.
> >
>
> Assuming recovery conflicts are relatively rare in tap tests, except
> for the explicitly designed tests like 031_recovery_conflict and the
> narrow timing window that the standby has not caught up while the wait
> for gets invoked, a simple fallback seems appropriate to me.

Yes, I see.  Seems acceptable given this seems the only feasible way to go.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-01-09 13:44                                                                                                                             ` Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-09 13:44 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Fri, Jan 9, 2026 at 4:42 AM Alexander Korotkov <[email protected]> wrote:
>
> On Thu, Jan 8, 2026 at 6:29 PM Xuneng Zhou <[email protected]> wrote:
> > On Thu, Jan 8, 2026 at 10:19 PM Alexander Korotkov <[email protected]> wrote:
> > > I see, you were right.  This is not related to the MyProc->xmin.
> > > ResolveRecoveryConflictWithTablespace() calls
> > > GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid).  That
> > > would kill WAIT FOR LSN query independently on its xmin.
> >
> > I think the concern is valid --- conflicts like
> > PROCSIG_RECOVERY_CONFLICT_SNAPSHOT could occur and terminate the
> > backend if the timing is unlucky. It's more difficult to reproduce
> > though. A check for the log containing "conflict with recovery" would
> > likely catch these conflicts as well.
>
> Yes, I found multiple reasons why xmin gets temporarily set during
> processing of WAIT FOR LSN query.  I'll soon post a draft patch to fix
> that.
>
> > > I guess your
> > > patch is the only way to go.  It's clumsy to wrap WAIT FOR LSN call
> > > with retry loop, but it would still consume less resources than
> > > polling.
> > >
> >
> > Assuming recovery conflicts are relatively rare in tap tests, except
> > for the explicitly designed tests like 031_recovery_conflict and the
> > narrow timing window that the standby has not caught up while the wait
> > for gets invoked, a simple fallback seems appropriate to me.
>
> Yes, I see.  Seems acceptable given this seems the only feasible way to go.
>

Here is the updated patch with recovery conflicts handled.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (6.3K, ../../CABPTF7WWxgAAr5fT9TFciU+PzeRpC3Dp7SO60AV9XWx561TNKA@mail.gmail.com/2-v1-0001-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From 1b1aa652aff6681e5f43eba4f4690b174052d478 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 9 Jan 2026 21:32:12 +0800
Subject: [PATCH v1] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.

Additionally, if the WAIT FOR LSN session is killed by a recovery
conflict (e.g., DROP TABLESPACE killing all backends indiscriminately),
the function catches this error and falls back to polling.  This makes
the test infrastructure robust against the timing-dependent conflicts
that can occur in tests like 031_recovery_conflict.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 91 +++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 955dfc0e7f8..87c3d2750cb 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,85 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write'  => 'standby_write',
+				'flush'  => 'standby_flush',
+			);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+
+			# Try WAIT FOR LSN. If it succeeds, we're done. If it returns a
+			# non-success status (timeout, not_in_recovery), fail immediately.
+			# If the session is interrupted (e.g., killed by recovery conflict),
+			# fall back to polling on the upstream which is immune to standby-
+			# side conflicts.
+			my $output;
+			local $@;
+			my $wait_succeeded = eval {
+				$output = $standby_node->safe_psql('postgres', $wait_query);
+				chomp($output);
+				1;
+			};
+
+			if ($wait_succeeded && $output eq 'success')
+			{
+				print "done\n";
+				return;
+			}
+
+			# If WAIT FOR LSN executed but returned non-success (e.g., timeout,
+			# not_in_recovery), fail immediately with diagnostic info. Falling
+			# back to polling would just waste time.
+			if ($wait_succeeded)
+			{
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+				croak "WAIT FOR LSN '$wait_mode' to '$target_lsn' returned '$output'";
+			}
+
+			# WAIT FOR LSN was interrupted. Only fall back to polling if this
+			# looks like a recovery conflict - the canonical PostgreSQL error
+			# message contains "conflict with recovery". Other errors should
+			# fail immediately rather than being masked by a silent fallback.
+			if ($@ =~ /conflict with recovery/i)
+			{
+				diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+			}
+			else
+			{
+				croak "WAIT FOR LSN failed: $@";
+			}
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-10 04:47                                                                                                                               ` Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-10 04:47 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Fri, Jan 9, 2026 at 9:44 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Fri, Jan 9, 2026 at 4:42 AM Alexander Korotkov <[email protected]> wrote:
> >
> > On Thu, Jan 8, 2026 at 6:29 PM Xuneng Zhou <[email protected]> wrote:
> > > On Thu, Jan 8, 2026 at 10:19 PM Alexander Korotkov <[email protected]> wrote:
> > > > I see, you were right.  This is not related to the MyProc->xmin.
> > > > ResolveRecoveryConflictWithTablespace() calls
> > > > GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid).  That
> > > > would kill WAIT FOR LSN query independently on its xmin.
> > >
> > > I think the concern is valid --- conflicts like
> > > PROCSIG_RECOVERY_CONFLICT_SNAPSHOT could occur and terminate the
> > > backend if the timing is unlucky. It's more difficult to reproduce
> > > though. A check for the log containing "conflict with recovery" would
> > > likely catch these conflicts as well.
> >
> > Yes, I found multiple reasons why xmin gets temporarily set during
> > processing of WAIT FOR LSN query.  I'll soon post a draft patch to fix
> > that.
> >
> > > > I guess your
> > > > patch is the only way to go.  It's clumsy to wrap WAIT FOR LSN call
> > > > with retry loop, but it would still consume less resources than
> > > > polling.
> > > >
> > >
> > > Assuming recovery conflicts are relatively rare in tap tests, except
> > > for the explicitly designed tests like 031_recovery_conflict and the
> > > narrow timing window that the standby has not caught up while the wait
> > > for gets invoked, a simple fallback seems appropriate to me.
> >
> > Yes, I see.  Seems acceptable given this seems the only feasible way to go.
> >
>
> Here is the updated patch with recovery conflicts handled.

V2 corrected the commit message to state " if the WAIT FOR LSN session
is interrupted by a recovery conflict (e.g., DROP TABLESPACE
triggering conflicts on all backends),".  In this case, the statement
is canceled when possible; in some states (idle in transaction or
subtransaction) the session may be terminated.


-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v2-0001-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (6.3K, ../../CABPTF7X0n=R50z2fBpj3EbYYz04Ab0-DHJa+JfoAEny62QmUdg@mail.gmail.com/2-v2-0001-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From 8d92735d473b974bfe53183615c448792ad209dc Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 9 Jan 2026 21:32:12 +0800
Subject: [PATCH v2] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')
- The standby is in recovery

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), or when the standby has
been promoted, the function falls back to the original polling-based
approach using pg_stat_replication on the upstream.

Additionally, if the WAIT FOR LSN session is interrupted by a recovery
conflict (e.g., DROP TABLESPACE triggering conflicts on all backends),
the function catches this error and falls back to polling.  This makes
the test infrastructure robust against the timing-dependent conflicts
that can occur in tests like 031_recovery_conflict.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 91 +++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 955dfc0e7f8..87c3d2750cb 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3320,6 +3320,13 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance and is
+in recovery, this function uses the WAIT FOR LSN command on the standby
+for modes replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  For 'sent' mode, or when the standby is passed as a
+string (e.g., a subscription name), it falls back to polling.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3339,10 +3346,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3367,6 +3377,85 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# - The standby is in recovery
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		my $standby_in_recovery =
+		  $standby_node->safe_psql('postgres', "SELECT pg_is_in_recovery()");
+		chomp($standby_in_recovery);
+
+		if ($standby_in_recovery eq 't')
+		{
+			# Map mode names to WAIT FOR LSN mode names
+			my %mode_map = (
+				'replay' => 'standby_replay',
+				'write'  => 'standby_write',
+				'flush'  => 'standby_flush',
+			);
+			my $wait_mode = $mode_map{$mode};
+			my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+			my $wait_query =
+			  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+
+			# Try WAIT FOR LSN. If it succeeds, we're done. If it returns a
+			# non-success status (timeout, not_in_recovery), fail immediately.
+			# If the session is interrupted (e.g., killed by recovery conflict),
+			# fall back to polling on the upstream which is immune to standby-
+			# side conflicts.
+			my $output;
+			local $@;
+			my $wait_succeeded = eval {
+				$output = $standby_node->safe_psql('postgres', $wait_query);
+				chomp($output);
+				1;
+			};
+
+			if ($wait_succeeded && $output eq 'success')
+			{
+				print "done\n";
+				return;
+			}
+
+			# If WAIT FOR LSN executed but returned non-success (e.g., timeout,
+			# not_in_recovery), fail immediately with diagnostic info. Falling
+			# back to polling would just waste time.
+			if ($wait_succeeded)
+			{
+				my $details = $self->safe_psql('postgres',
+					"SELECT * FROM pg_catalog.pg_stat_replication");
+				diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+				croak "WAIT FOR LSN '$wait_mode' to '$target_lsn' returned '$output'";
+			}
+
+			# WAIT FOR LSN was interrupted. Only fall back to polling if this
+			# looks like a recovery conflict - the canonical PostgreSQL error
+			# message contains "conflict with recovery". Other errors should
+			# fail immediately rather than being masked by a silent fallback.
+			if ($@ =~ /conflict with recovery/i)
+			{
+				diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+			}
+			else
+			{
+				croak "WAIT FOR LSN failed: $@";
+			}
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-12 06:53                                                                                                                                 ` Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-12 06:53 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Sat, Jan 10, 2026 at 12:47 PM Xuneng Zhou <[email protected]> wrote:
>
> On Fri, Jan 9, 2026 at 9:44 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Fri, Jan 9, 2026 at 4:42 AM Alexander Korotkov <[email protected]> wrote:
> > >
> > > On Thu, Jan 8, 2026 at 6:29 PM Xuneng Zhou <[email protected]> wrote:
> > > > On Thu, Jan 8, 2026 at 10:19 PM Alexander Korotkov <[email protected]> wrote:
> > > > > I see, you were right.  This is not related to the MyProc->xmin.
> > > > > ResolveRecoveryConflictWithTablespace() calls
> > > > > GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid).  That
> > > > > would kill WAIT FOR LSN query independently on its xmin.
> > > >
> > > > I think the concern is valid --- conflicts like
> > > > PROCSIG_RECOVERY_CONFLICT_SNAPSHOT could occur and terminate the
> > > > backend if the timing is unlucky. It's more difficult to reproduce
> > > > though. A check for the log containing "conflict with recovery" would
> > > > likely catch these conflicts as well.
> > >
> > > Yes, I found multiple reasons why xmin gets temporarily set during
> > > processing of WAIT FOR LSN query.  I'll soon post a draft patch to fix
> > > that.
> > >
> > > > > I guess your
> > > > > patch is the only way to go.  It's clumsy to wrap WAIT FOR LSN call
> > > > > with retry loop, but it would still consume less resources than
> > > > > polling.
> > > > >
> > > >
> > > > Assuming recovery conflicts are relatively rare in tap tests, except
> > > > for the explicitly designed tests like 031_recovery_conflict and the
> > > > narrow timing window that the standby has not caught up while the wait
> > > > for gets invoked, a simple fallback seems appropriate to me.
> > >
> > > Yes, I see.  Seems acceptable given this seems the only feasible way to go.
> > >
> >
> > Here is the updated patch with recovery conflicts handled.
>
> V2 corrected the commit message to state " if the WAIT FOR LSN session
> is interrupted by a recovery conflict (e.g., DROP TABLESPACE
> triggering conflicts on all backends),".  In this case, the statement
> is canceled when possible; in some states (idle in transaction or
> subtransaction) the session may be terminated.
>

The attached patch avoids a syscache lookup while constructing the
tuple descriptor for WAIT FOR LSN, so that a catalog snapshot is not
re-established after the wait finishes.

The standard output path (printtup) may still briefly establish a
catalog snapshot during result emission, but this seems acceptable:
the snapshot window is narrow to emit a single row. A fully
catalog-free output path would require either bypassing the
DestReceiver lifecycle (breaking layering) or adding a custom receiver
(added complexity for marginal benefit). The current approach is
simpler and might be sufficient unless output-phase conflicts are
observed a lot in practice. Does this make sense to you?

--
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Avoid-syscache-lookup-in-WAIT-FOR-LSN-tuple-descr.patch (2.4K, ../../CABPTF7U+SUnJX_woQYGe==R9Oz+-V6X0VO2stBLPGfJmH_LEhw@mail.gmail.com/2-v1-0001-Avoid-syscache-lookup-in-WAIT-FOR-LSN-tuple-descr.patch)
  download | inline diff:
From 50beec4de4e078020b03667453458cd440c26267 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Mon, 12 Jan 2026 14:36:27 +0800
Subject: [PATCH v1] Avoid syscache lookup in WAIT FOR LSN tuple descriptor

Use TupleDescInitBuiltinEntry instead of TupleDescInitEntry when
building the result tuple descriptor for WAIT FOR LSN. This avoids
a syscache access that could re-establish a catalog snapshot after
we've explicitly released all snapshots before the wait.
---
 src/backend/commands/wait.c | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 97f1e778488..191c1877125 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -15,6 +15,7 @@
 
 #include <math.h>
 
+#include "access/tupdesc.h"
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
@@ -320,7 +321,17 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 			break;
 	}
 
-	/* need a tuple descriptor representing a single TEXT column */
+	/*
+	 * Output the result.
+	 *
+	 * We use TupleDescInitBuiltinEntry in WaitStmtResultDesc to avoid
+	 * syscache access when building the tuple descriptor. The standard output
+	 * path may briefly establish a catalog snapshot during output, but this
+	 * is acceptable since: 1. The snapshot window is very brief (just
+	 * emitting one row) 2. The critical section (the wait itself) is already
+	 * snapshot-free 3. Using the standard path respects receiver lifecycle
+	 * and semantics
+	 */
 	tupdesc = WaitStmtResultDesc(stmt);
 
 	/* prepare for projection of tuples */
@@ -337,9 +348,16 @@ WaitStmtResultDesc(WaitStmt *stmt)
 {
 	TupleDesc	tupdesc;
 
-	/* Need a tuple descriptor representing a single TEXT  column */
+	/*
+	 * Need a tuple descriptor representing a single TEXT column.
+	 *
+	 * We use TupleDescInitBuiltinEntry instead of TupleDescInitEntry to avoid
+	 * syscache access. This is important because WaitStmtResultDesc may be
+	 * called after snapshots have been released, and we must not re-establish
+	 * a catalog snapshot which could cause recovery conflicts on a standby.
+	 */
 	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
-					   TEXTOID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "status",
+							  TEXTOID, -1, 0);
 	return tupdesc;
 }
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-20 01:28                                                                                                                                   ` Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-20 01:28 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

Peter pointed out redundant pg_unreachable() calls after elog(ERROR)
in wait.c. Attached patch removes them.

--
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-WAIT-FOR-Remove-redundant-pg_unreachable-after-el.patch (1.3K, ../../CABPTF7UcuVD0L-X=jZFfeygjPaZWWkVRwtWOaJw2tcXbEN2xsA@mail.gmail.com/2-v1-0001-WAIT-FOR-Remove-redundant-pg_unreachable-after-el.patch)
  download | inline diff:
From b7e3036d1872b96303f7defdad2f6f9332e89f8e Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 20 Jan 2026 08:40:24 +0800
Subject: [PATCH v1] WAIT FOR: Remove redundant pg_unreachable() after
 elog(ERROR)

elog(ERROR) never returns, so the following pg_unreachable() calls
are unnecessary. This pattern is useful for non-void functions, but
these cases are in a void function.
---
 src/backend/commands/wait.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 54f2df2425f..212a0407871 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -236,7 +236,6 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 					default:
 						elog(ERROR, "unexpected wait LSN type %d", lsnType);
-						pg_unreachable();
 				}
 			}
 			else
@@ -281,7 +280,6 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 						default:
 							elog(ERROR, "unexpected wait LSN type %d", lsnType);
-							pg_unreachable();
 					}
 				}
 				else
@@ -311,7 +309,6 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 
 						default:
 							elog(ERROR, "unexpected wait LSN type %d", lsnType);
-							pg_unreachable();
 					}
 				}
 			}
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-27 01:14                                                                                                                                     ` Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-01-27 01:14 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

Heikki spotted a misplaced wake-up call for replay waiters in
PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
invoked immediately after wal record is applied to avoid the potential
missed wake-ups when recovery stops/pauses/promotes. It makes sense to
me. Please check the attached patch to fix that.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Wake-LSN-waiters-before-recovery-target-stop.patch (1.6K, ../../CABPTF7Wdq6KbvC3EhLX3Pz=ODCCPEX7qVQ+E=cokkB91an2E-A@mail.gmail.com/2-v1-0001-Wake-LSN-waiters-before-recovery-target-stop.patch)
  download | inline diff:
From 3e692c636838a928918266174c3e139274384702 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 27 Jan 2026 09:00:39 +0800
Subject: [PATCH v1] Wake LSN waiters before recovery target stop

  Move WaitLSNWakeup() immediately after ApplyWalRecord() so waiters are
  signaled even when recoveryStopsAfter() breaks out for pause/promotion
  targets.
---
 src/backend/access/transam/xlogrecovery.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b9393e551b7..36daa3d8587 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1777,13 +1777,6 @@ PerformWalRecovery(void)
 			 */
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
-			/* Exit loop if we reached inclusive recovery target */
-			if (recoveryStopsAfter(xlogreader))
-			{
-				reachedRecoveryTarget = true;
-				break;
-			}
-
 			/*
 			 * If we replayed an LSN that someone was waiting for then walk
 			 * over the shared memory array and set latches to notify the
@@ -1794,6 +1787,13 @@ PerformWalRecovery(void)
 				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
 				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
 
+			/* Exit loop if we reached inclusive recovery target */
+			if (recoveryStopsAfter(xlogreader))
+			{
+				reachedRecoveryTarget = true;
+				break;
+			}
+
 			/* Else, try to fetch the next WAL record */
 			record = ReadRecord(xlogprefetcher, LOG, false, replayTLI);
 		} while (record != NULL);
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-01-29 07:47                                                                                                                                       ` Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-29 07:47 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Jan 27, 2026 at 3:14 AM Xuneng Zhou <[email protected]> wrote:
> Heikki spotted a misplaced wake-up call for replay waiters in
> PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
> invoked immediately after wal record is applied to avoid the potential
> missed wake-ups when recovery stops/pauses/promotes. It makes sense to
> me. Please check the attached patch to fix that.

Pushed, thank you!

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-05 12:31                                                                                                                                         ` Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-05 12:31 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Jan 29, 2026 at 9:47 AM Alexander Korotkov <[email protected]> wrote:
> On Tue, Jan 27, 2026 at 3:14 AM Xuneng Zhou <[email protected]> wrote:
> > Heikki spotted a misplaced wake-up call for replay waiters in
> > PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
> > invoked immediately after wal record is applied to avoid the potential
> > missed wake-ups when recovery stops/pauses/promotes. It makes sense to
> > me. Please check the attached patch to fix that.
>
> Pushed, thank you!

I've assembled small patches, which I think worth pushing before v19 FF.

1. Avoid syscache lookup in WaitStmtResultDesc().  This is [1] patch,
but I've removed redundant comment in ExecWaitStmt(), which explained
the same as WaitStmtResultDesc() comment.
2. Use WAIT FOR LSN in wait_for_catchup().  I made the following
changes: fallback to polling on not_in_recovery result instead of
croaking, avoid separate pg_is_in_recovery() query, comment why we
may face the recovery conflict and why it is safe to detect this by
the error string.
3. A paragraph to the docs about possible recovery conflicts and their reason.

I'm going to push this on Monday if no objections.

Links.
1. https://www.postgresql.org/message-id/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40...
2. https://www.postgresql.org/message-id/CABPTF7X0n%3DR50z2fBpj3EbYYz04Ab0-DHJa%2BJfoAEny62QmUdg%40mail...

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v3-0001-Avoid-syscache-lookup-while-building-a-WAIT-FOR-t.patch (1.8K, ../../CAPpHfds7oSCbZqob7ytT_Lso8fv-NW8LnedUTE4Krde+3rkJeA@mail.gmail.com/2-v3-0001-Avoid-syscache-lookup-while-building-a-WAIT-FOR-t.patch)
  download | inline diff:
From 526678dc7c19ecf6a26f4dbb70f73441ab2de817 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sun, 5 Apr 2026 14:11:08 +0300
Subject: [PATCH v3 1/3] Avoid syscache lookup while building a WAIT FOR tuple
 descriptor

Use TupleDescInitBuiltinEntry instead of TupleDescInitEntry when building
the result tuple descriptor for the WAIT FOR command. This avoids a syscache
access that could re-establish a catalog snapshot after we've explicitly
released all snapshots before the wait.

Discussion: https://postgr.es/m/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
---
 src/backend/commands/wait.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index e64e3be6285..85fcd463b4c 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -335,10 +335,17 @@ WaitStmtResultDesc(WaitStmt *stmt)
 {
 	TupleDesc	tupdesc;
 
-	/* Need a tuple descriptor representing a single TEXT  column */
+	/*
+	 * Need a tuple descriptor representing a single TEXT column.
+	 *
+	 * We use TupleDescInitBuiltinEntry instead of TupleDescInitEntry to avoid
+	 * syscache access. This is important because WaitStmtResultDesc may be
+	 * called after snapshots have been released, and we must not re-establish
+	 * a catalog snapshot which could cause recovery conflicts on a standby.
+	 */
 	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
-					   TEXTOID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "status",
+							  TEXTOID, -1, 0);
 	TupleDescFinalize(tupdesc);
 	return tupdesc;
 }
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0003-Document-that-WAIT-FOR-may-be-interrupted-by-reco.patch (1.5K, ../../CAPpHfds7oSCbZqob7ytT_Lso8fv-NW8LnedUTE4Krde+3rkJeA@mail.gmail.com/3-v3-0003-Document-that-WAIT-FOR-may-be-interrupted-by-reco.patch)
  download | inline diff:
From c6b13967bfec491b9743ab55bb44aec4915714ab Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sun, 5 Apr 2026 15:17:04 +0300
Subject: [PATCH v3 3/3] Document that WAIT FOR may be interrupted by recovery
 conflicts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a note to the WAIT FOR documentation explaining that sessions
using this command on a standby server may be interrupted by recovery
conflicts.  Some conflicts are unavoidable — for example, replaying
a tablespace drop terminates all backends unconditionally.
---
 doc/src/sgml/ref/wait_for.sgml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index df72b3327c8..c30fba6f05a 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -253,6 +253,16 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    timeline.
   </para>
 
+  <para>
+   On a standby server, <command>WAIT FOR</command> sessions may be
+   interrupted by recovery conflicts.  Some recovery conflicts are
+   unavoidable: for example, replaying a tablespace drop resolves
+   conflicts by terminating all backends, regardless of what they are
+   doing.  Applications using <command>WAIT FOR</command> on a standby
+   should be prepared to handle such interruptions, for example by
+   retrying the command or falling back to an alternative mechanism.
+  </para>
+
 </refsect1>
 
  <refsect1>
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0002-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (7.0K, ../../CAPpHfds7oSCbZqob7ytT_Lso8fv-NW8LnedUTE4Krde+3rkJeA@mail.gmail.com/4-v3-0002-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From ce0425e698b9da5201a1bbd6565b1087c065c2c0 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sun, 5 Apr 2026 14:46:47 +0300
Subject: [PATCH v3 2/3] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

Rather than pre-checking pg_is_in_recovery() on the standby (which
would add an extra round-trip on every call), we issue WAIT FOR LSN
directly and handle the 'not_in_recovery' result as a signal to fall
back to polling.

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), when the standby has been
promoted, or when WAIT FOR LSN is interrupted by a recovery conflict,
the function falls back to the original polling-based approach using
pg_stat_replication on the upstream.  The recovery conflict fallback
is necessary because some conflicts are unavoidable — for example,
ResolveRecoveryConflictWithTablespace() kills all backends
unconditionally, regardless of what they are doing.

The recovery conflict detection matches the English error message
"conflict with recovery", which is reliable because the test suite
runs with LC_MESSAGES=C.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 94 +++++++++++++++++++++++-
 1 file changed, 93 insertions(+), 1 deletion(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 54e6b646e8f..f51b8a65e58 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3355,6 +3355,15 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
+When the standby is passed as a PostgreSQL::Test::Cluster instance, this
+function attempts to use the WAIT FOR LSN command on the standby for modes
+replay, write, and flush.  This is more efficient than polling
+pg_stat_replication on the upstream, as WAIT FOR LSN uses a latch-based
+wakeup mechanism.  If the standby has been promoted, or if the session is
+interrupted by a recovery conflict, it falls back to polling.  For 'sent'
+mode, or when the standby is passed as a string (e.g., a subscription
+name), polling is used directly.
+
 If there is no active replication connection from this peer, waits until
 poll_query_until timeout.
 
@@ -3374,10 +3383,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3402,6 +3414,86 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	#
+	# We skip the pg_is_in_recovery() pre-check and just attempt WAIT FOR
+	# LSN directly.  If the standby was promoted, it returns 'not_in_recovery'
+	# and we fall back to polling.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		# Map mode names to WAIT FOR LSN mode names
+		my %mode_map = (
+			'replay' => 'standby_replay',
+			'write' => 'standby_write',
+			'flush' => 'standby_flush',);
+		my $wait_mode = $mode_map{$mode};
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		my $wait_query =
+		  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+
+		# Try WAIT FOR LSN. If it succeeds, we're done. If it returns
+		# 'not_in_recovery' (standby was promoted), fall back to polling.
+		# If the session is interrupted (e.g., killed by recovery conflict),
+		# fall back to polling on the upstream which is immune to standby-
+		# side conflicts.
+		my $output;
+		local $@;
+		my $wait_succeeded = eval {
+			$output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+			1;
+		};
+
+		if ($wait_succeeded && $output eq 'success')
+		{
+			print "done\n";
+			return;
+		}
+
+		# 'not_in_recovery' means the standby was promoted; fall back to
+		# polling pg_stat_replication on the upstream.
+		if ($wait_succeeded && $output eq 'not_in_recovery')
+		{
+			diag
+			  "WAIT FOR LSN returned 'not_in_recovery', falling back to polling";
+		}
+		# 'timeout' is a hard failure — no point falling back to polling.
+		elsif ($wait_succeeded)
+		{
+			my $details = $self->safe_psql('postgres',
+				"SELECT * FROM pg_catalog.pg_stat_replication");
+			diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+			croak
+			  "WAIT FOR LSN '$wait_mode' to '$target_lsn' returned '$output'";
+		}
+		# WAIT FOR LSN was interrupted.  Fall back to polling if this
+		# looks like a recovery conflict.  We match the English error
+		# message "conflict with recovery" which is reliable because the
+		# test suite runs with LC_MESSAGES=C.  Other errors should fail
+		# immediately rather than being masked by a silent fallback.
+		elsif ($@ =~ /conflict with recovery/i)
+		{
+			diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+		}
+		else
+		{
+			croak "WAIT FOR LSN failed: $@";
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-06 03:26                                                                                                                                           ` Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-06 03:26 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Sun, Apr 5, 2026 at 8:31 PM Alexander Korotkov <[email protected]> wrote:
>
> On Thu, Jan 29, 2026 at 9:47 AM Alexander Korotkov <[email protected]> wrote:
> > On Tue, Jan 27, 2026 at 3:14 AM Xuneng Zhou <[email protected]> wrote:
> > > Heikki spotted a misplaced wake-up call for replay waiters in
> > > PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
> > > invoked immediately after wal record is applied to avoid the potential
> > > missed wake-ups when recovery stops/pauses/promotes. It makes sense to
> > > me. Please check the attached patch to fix that.
> >
> > Pushed, thank you!
>
> I've assembled small patches, which I think worth pushing before v19 FF.
>
> 1. Avoid syscache lookup in WaitStmtResultDesc().  This is [1] patch,
> but I've removed redundant comment in ExecWaitStmt(), which explained
> the same as WaitStmtResultDesc() comment.
> 2. Use WAIT FOR LSN in wait_for_catchup().  I made the following
> changes: fallback to polling on not_in_recovery result instead of
> croaking, avoid separate pg_is_in_recovery() query, comment why we
> may face the recovery conflict and why it is safe to detect this by
> the error string.
> 3. A paragraph to the docs about possible recovery conflicts and their reason.
>
> I'm going to push this on Monday if no objections.
>
> Links.
> 1. https://www.postgresql.org/message-id/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40...
> 2. https://www.postgresql.org/message-id/CABPTF7X0n%3DR50z2fBpj3EbYYz04Ab0-DHJa%2BJfoAEny62QmUdg%40mail...

I'll review them shortly.


--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-06 06:01                                                                                                                                             ` Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-06 06:01 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Mon, Apr 6, 2026 at 11:26 AM Xuneng Zhou <[email protected]> wrote:
>
> Hi Alexander,
>
> On Sun, Apr 5, 2026 at 8:31 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Thu, Jan 29, 2026 at 9:47 AM Alexander Korotkov <[email protected]> wrote:
> > > On Tue, Jan 27, 2026 at 3:14 AM Xuneng Zhou <[email protected]> wrote:
> > > > Heikki spotted a misplaced wake-up call for replay waiters in
> > > > PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
> > > > invoked immediately after wal record is applied to avoid the potential
> > > > missed wake-ups when recovery stops/pauses/promotes. It makes sense to
> > > > me. Please check the attached patch to fix that.
> > >
> > > Pushed, thank you!
> >
> > I've assembled small patches, which I think worth pushing before v19 FF.
> >
> > 1. Avoid syscache lookup in WaitStmtResultDesc().  This is [1] patch,
> > but I've removed redundant comment in ExecWaitStmt(), which explained
> > the same as WaitStmtResultDesc() comment.
> > 2. Use WAIT FOR LSN in wait_for_catchup().  I made the following
> > changes: fallback to polling on not_in_recovery result instead of
> > croaking, avoid separate pg_is_in_recovery() query, comment why we
> > may face the recovery conflict and why it is safe to detect this by
> > the error string.
> > 3. A paragraph to the docs about possible recovery conflicts and their reason.
> >
> > I'm going to push this on Monday if no objections.
> >
> > Links.
> > 1. https://www.postgresql.org/message-id/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40...
> > 2. https://www.postgresql.org/message-id/CABPTF7X0n%3DR50z2fBpj3EbYYz04Ab0-DHJa%2BJfoAEny62QmUdg%40mail...
>
> I'll review them shortly.
>

Here're some comments:

1) Patch 2 checks for not_in_recovery, but WAIT FOR ... NO_THROW
returns “not in recovery”, which appears to prevent the
promoted-standby fallback described in the patch from triggering.
Instead, it seems to result in a hard failure.

There are some behavior changes that I overlooked earlier:

2)  Patch 2 changes the helper from "wait until timeout if there is no
active replication connection" to "connect to the standby immediately
and fail on ordinary connection failures". This appears to differ from
the current contract and may affect cases where the standby is down,
restarting, or not yet accepting SQL.

"If there is no active replication connection from this peer, waits
until poll_query_until timeout."

3) In patch 2, we returns success as soon as the standby locally
reaches the target LSN, but the existing helper is explicitly defined
in terms of pg_stat_replication ... state = 'streaming' on the
upstream. So the new path can report success even when there is no
active replication connection from that peer anymore.

"The replication connection must be in a streaming state."

I’m not sure whether these semantic changes are intended.


--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-06 07:15                                                                                                                                               ` Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-06 07:15 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Mon, Apr 6, 2026 at 2:01 PM Xuneng Zhou <[email protected]> wrote:
>
> On Mon, Apr 6, 2026 at 11:26 AM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi Alexander,
> >
> > On Sun, Apr 5, 2026 at 8:31 PM Alexander Korotkov <[email protected]> wrote:
> > >
> > > On Thu, Jan 29, 2026 at 9:47 AM Alexander Korotkov <[email protected]> wrote:
> > > > On Tue, Jan 27, 2026 at 3:14 AM Xuneng Zhou <[email protected]> wrote:
> > > > > Heikki spotted a misplaced wake-up call for replay waiters in
> > > > > PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
> > > > > invoked immediately after wal record is applied to avoid the potential
> > > > > missed wake-ups when recovery stops/pauses/promotes. It makes sense to
> > > > > me. Please check the attached patch to fix that.
> > > >
> > > > Pushed, thank you!
> > >
> > > I've assembled small patches, which I think worth pushing before v19 FF.
> > >
> > > 1. Avoid syscache lookup in WaitStmtResultDesc().  This is [1] patch,
> > > but I've removed redundant comment in ExecWaitStmt(), which explained
> > > the same as WaitStmtResultDesc() comment.
> > > 2. Use WAIT FOR LSN in wait_for_catchup().  I made the following
> > > changes: fallback to polling on not_in_recovery result instead of
> > > croaking, avoid separate pg_is_in_recovery() query, comment why we
> > > may face the recovery conflict and why it is safe to detect this by
> > > the error string.
> > > 3. A paragraph to the docs about possible recovery conflicts and their reason.
> > >
> > > I'm going to push this on Monday if no objections.
> > >
> > > Links.
> > > 1. https://www.postgresql.org/message-id/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40...
> > > 2. https://www.postgresql.org/message-id/CABPTF7X0n%3DR50z2fBpj3EbYYz04Ab0-DHJa%2BJfoAEny62QmUdg%40mail...
> >
> > I'll review them shortly.
> >
>
> Here're some comments:
>
> 1) Patch 2 checks for not_in_recovery, but WAIT FOR ... NO_THROW
> returns “not in recovery”, which appears to prevent the
> promoted-standby fallback described in the patch from triggering.
> Instead, it seems to result in a hard failure.
>
> There are some behavior changes that I overlooked earlier:
>
> 2)  Patch 2 changes the helper from "wait until timeout if there is no
> active replication connection" to "connect to the standby immediately
> and fail on ordinary connection failures". This appears to differ from
> the current contract and may affect cases where the standby is down,
> restarting, or not yet accepting SQL.
>
> "If there is no active replication connection from this peer, waits
> until poll_query_until timeout."
>
> 3) In patch 2, we returns success as soon as the standby locally
> reaches the target LSN, but the existing helper is explicitly defined
> in terms of pg_stat_replication ... state = 'streaming' on the
> upstream. So the new path can report success even when there is no
> active replication connection from that peer anymore.
>
> "The replication connection must be in a streaming state."
>
> I’m not sure whether these semantic changes are intended.
>

After some thoughts, these semantic changes appear to be improvements
over the artifacts of polling-based behavior. The pg_stat_replication
... state = 'streaming' is a precondition of polling works rather than
a post-condition check. The retry timeout also has nothing to do with
the edge cases and the state of the standby.

In practice, wait_for_catchup() is called when the standby is expected
to be up. If it's not, one of two things is true:

The standby is briefly restarting -> start() already waits for
readiness before returning, so this is a non-issue.
The standby is genuinely down -> waiting 180 seconds to time out
wastes CI time compared to failing fast.

I’ve addressed point 1 and updated the comments accordingly to reflect
the new behavior. Please check it.


--
Best,
Xuneng


Attachments:

  [application/x-patch] v4-0001-Avoid-syscache-lookup-while-building-a-WAIT-FOR-t.patch (1.8K, ../../CABPTF7V-E_e3kQ2vtwUz6Jy7u-8_YeUT0SDoAbu7EKPgNp=ndA@mail.gmail.com/2-v4-0001-Avoid-syscache-lookup-while-building-a-WAIT-FOR-t.patch)
  download | inline diff:
From a44b45214182807f629f16f13a0ad4e55aaab86a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Mon, 6 Apr 2026 14:25:36 +0800
Subject: [PATCH v4 1/3]  Avoid syscache lookup while building a WAIT FOR tuple
  descriptor

Use TupleDescInitBuiltinEntry instead of TupleDescInitEntry when building
the result tuple descriptor for the WAIT FOR command. This avoids a syscache
access that could re-establish a catalog snapshot after we've explicitly
released all snapshots before the wait.

Discussion: https://postgr.es/m/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
---
 src/backend/commands/wait.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index e64e3be6285..85fcd463b4c 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -335,10 +335,17 @@ WaitStmtResultDesc(WaitStmt *stmt)
 {
 	TupleDesc	tupdesc;
 
-	/* Need a tuple descriptor representing a single TEXT  column */
+	/*
+	 * Need a tuple descriptor representing a single TEXT column.
+	 *
+	 * We use TupleDescInitBuiltinEntry instead of TupleDescInitEntry to avoid
+	 * syscache access. This is important because WaitStmtResultDesc may be
+	 * called after snapshots have been released, and we must not re-establish
+	 * a catalog snapshot which could cause recovery conflicts on a standby.
+	 */
 	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
-					   TEXTOID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "status",
+							  TEXTOID, -1, 0);
 	TupleDescFinalize(tupdesc);
 	return tupdesc;
 }
-- 
2.51.0



  [application/x-patch] v4-0003-Document-that-WAIT-FOR-may-be-interrupted-by-reco.patch (1.5K, ../../CABPTF7V-E_e3kQ2vtwUz6Jy7u-8_YeUT0SDoAbu7EKPgNp=ndA@mail.gmail.com/3-v4-0003-Document-that-WAIT-FOR-may-be-interrupted-by-reco.patch)
  download | inline diff:
From b6acf00fa2c8cf8f29dbd87b674fc88f99ac916f Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Mon, 6 Apr 2026 14:52:58 +0800
Subject: [PATCH v4 3/3] Document that WAIT FOR may be interrupted by recovery
 conflicts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a note to the WAIT FOR documentation explaining that sessions
using this command on a standby server may be interrupted by recovery
conflicts.  Some conflicts are unavoidable — for example, replaying
a tablespace drop terminates all backends unconditionally.
---
 doc/src/sgml/ref/wait_for.sgml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index df72b3327c8..c30fba6f05a 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -253,6 +253,16 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    timeline.
   </para>
 
+  <para>
+   On a standby server, <command>WAIT FOR</command> sessions may be
+   interrupted by recovery conflicts.  Some recovery conflicts are
+   unavoidable: for example, replaying a tablespace drop resolves
+   conflicts by terminating all backends, regardless of what they are
+   doing.  Applications using <command>WAIT FOR</command> on a standby
+   should be prepared to handle such interruptions, for example by
+   retrying the command or falling back to an alternative mechanism.
+  </para>
+
 </refsect1>
 
  <refsect1>
-- 
2.51.0



  [application/x-patch] v4-0002-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch (7.7K, ../../CABPTF7V-E_e3kQ2vtwUz6Jy7u-8_YeUT0SDoAbu7EKPgNp=ndA@mail.gmail.com/4-v4-0002-Use-WAIT-FOR-LSN-in-PostgreSQL-Test-Cluster-wait_.patch)
  download | inline diff:
From 8a5001f7a443c070fb9247c4d0e6a194b1ee5dc1 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Mon, 6 Apr 2026 14:51:08 +0800
Subject: [PATCH v4 2/3] Use WAIT FOR LSN in
 PostgreSQL::Test::Cluster::wait_for_catchup()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When the standby is passed as a PostgreSQL::Test::Cluster instance,
use the WAIT FOR LSN command on the standby server to implement
wait_for_catchup() for replay, write, and flush modes.  This is more
efficient than polling pg_stat_replication on the upstream, as the
WAIT FOR LSN command uses a latch-based wakeup mechanism.

The optimization applies when:
- The standby is passed as a Cluster object (not just a name string)
- The mode is 'replay', 'write', or 'flush' (not 'sent')

Rather than pre-checking pg_is_in_recovery() on the standby (which
would add an extra round-trip on every call), we issue WAIT FOR LSN
directly and handle the 'not in recovery' result as a signal to fall
back to polling.

For 'sent' mode, when the standby is passed as a string (e.g., a
subscription name for logical replication), when the standby has been
promoted, or when WAIT FOR LSN is interrupted by a recovery conflict,
the function falls back to the original polling-based approach using
pg_stat_replication on the upstream.  The recovery conflict fallback
is necessary because some conflicts are unavoidable — for example,
ResolveRecoveryConflictWithTablespace() kills all backends
unconditionally, regardless of what they are doing.

The recovery conflict detection matches the English error message
"conflict with recovery", which is reliable because the test suite
runs with LC_MESSAGES=C.

Discussion: https://postgr.es/m/CABPTF7UiArgW-sXj9CNwRzUhYOQrevLzkYcgBydmX5oDes1sjg%40mail.gmail.com
Author: Xuneng Zhou <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
---
 src/test/perl/PostgreSQL/Test/Cluster.pm | 103 +++++++++++++++++++++--
 1 file changed, 95 insertions(+), 8 deletions(-)

diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index b44aefb545a..8058c95b429 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3333,11 +3333,8 @@ sub wait_for_event
 
 =item $node->wait_for_catchup(standby_name, mode, target_lsn)
 
-Wait for the replication connection with application_name standby_name until
-its 'mode' replication column in pg_stat_replication equals or passes the
-specified or default target_lsn.  By default the replay_lsn is waited for,
-but 'mode' may be specified to wait for any of sent|write|flush|replay.
-The replication connection must be in a streaming state.
+Wait until the standby identified by standby_name has reached the specified
+or default target_lsn for the given mode (sent|write|flush|replay).
 
 When doing physical replication, the standby is usually identified by
 passing its PostgreSQL::Test::Cluster instance.  When doing logical
@@ -3355,8 +3352,16 @@ If you pass an explicit value of target_lsn, it should almost always be
 the primary's write LSN; so this parameter is seldom needed except when
 querying some intermediate replication node rather than the primary.
 
-If there is no active replication connection from this peer, waits until
-poll_query_until timeout.
+When the standby is passed as a PostgreSQL::Test::Cluster instance and the
+mode is replay, write, or flush, the function uses WAIT FOR LSN on the
+standby for latch-based wakeup instead of polling.  If the standby has been
+promoted, if the session is interrupted by a recovery conflict, or if the
+standby is unreachable, it falls back to polling.
+
+For 'sent' mode, when the standby is passed as a string (e.g., a
+subscription name), or as a fallback from the above, the function polls
+pg_stat_replication on the upstream.  The replication connection must be
+in a streaming state for this path.
 
 Requires that the 'postgres' db exists and is accessible.
 
@@ -3374,10 +3379,13 @@ sub wait_for_catchup
 	  . join(', ', keys(%valid_modes))
 	  unless exists($valid_modes{$mode});
 
-	# Allow passing of a PostgreSQL::Test::Cluster instance as shorthand
+	# Keep a reference to the standby node if passed as an object, so we can
+	# use WAIT FOR LSN on it later.
+	my $standby_node;
 	if (blessed($standby_name)
 		&& $standby_name->isa("PostgreSQL::Test::Cluster"))
 	{
+		$standby_node = $standby_name;
 		$standby_name = $standby_name->name;
 	}
 	if (!defined($target_lsn))
@@ -3402,6 +3410,85 @@ sub wait_for_catchup
 	  . $self->name . "\n";
 	# Before release 12 walreceiver just set the application name to
 	# "walreceiver"
+
+	# Use WAIT FOR LSN on the standby when:
+	# - The standby was passed as a Cluster object (so we can connect to it)
+	# - The mode is replay, write, or flush (not 'sent')
+	# This is more efficient than polling pg_stat_replication on the upstream,
+	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	#
+	# We skip the pg_is_in_recovery() pre-check and just attempt WAIT FOR
+	# LSN directly.  If the standby was promoted, it returns 'not_in_recovery'
+	# and we fall back to polling.
+	if (defined($standby_node) && ($mode ne 'sent'))
+	{
+		# Map mode names to WAIT FOR LSN mode names
+		my %mode_map = (
+			'replay' => 'standby_replay',
+			'write' => 'standby_write',
+			'flush' => 'standby_flush',);
+		my $wait_mode = $mode_map{$mode};
+		my $timeout = $PostgreSQL::Test::Utils::timeout_default;
+		my $wait_query =
+		  qq[WAIT FOR LSN '${target_lsn}' WITH (MODE '${wait_mode}', timeout '${timeout}s', no_throw);];
+
+		# Try WAIT FOR LSN. If it succeeds, we're done. If it returns
+		# 'not_in_recovery' (standby was promoted), fall back to polling.
+		# If the session is interrupted (e.g., killed by recovery conflict),
+		# fall back to polling on the upstream which is immune to standby-
+		# side conflicts.
+		my $output;
+		local $@;
+		my $wait_succeeded = eval {
+			$output = $standby_node->safe_psql('postgres', $wait_query);
+			chomp($output);
+			1;
+		};
+
+		if ($wait_succeeded && $output eq 'success')
+		{
+			print "done\n";
+			return;
+		}
+
+		# 'not in recovery' means the standby was promoted.
+		if ($wait_succeeded && $output eq 'not in recovery')
+		{
+			diag
+			  "WAIT FOR LSN returned 'not in recovery', falling back to polling";
+		}
+		# 'timeout' is a hard failure — no point falling back to polling.
+		elsif ($wait_succeeded)
+		{
+			my $details = $self->safe_psql('postgres',
+				"SELECT * FROM pg_catalog.pg_stat_replication");
+			diag qq(WAIT FOR LSN returned '$output'
+pg_stat_replication on upstream:
+${details});
+			croak
+			  "WAIT FOR LSN '$wait_mode' to '$target_lsn' returned '$output'";
+		}
+		# WAIT FOR LSN was interrupted.  Fall back to polling if this
+		# looks like a recovery conflict.  We match the English error
+		# message "conflict with recovery" which is reliable because the
+		# test suite runs with LC_MESSAGES=C.  Other errors should fail
+		# immediately rather than being masked by a silent fallback.
+		elsif ($@ =~ /conflict with recovery/i)
+		{
+			diag qq(WAIT FOR LSN interrupted, falling back to polling:
+$@);
+		}
+		else
+		{
+			croak "WAIT FOR LSN failed: $@";
+		}
+	}
+
+	# Fall back to polling pg_stat_replication on the upstream for:
+	# - 'sent' mode (no corresponding WAIT FOR LSN mode)
+	# - When standby_name is a string (e.g., subscription name)
+	# - When the standby is no longer in recovery (was promoted)
+	# - When WAIT FOR LSN was interrupted (e.g., killed by a recovery conflict)
 	my $query = qq[SELECT '$target_lsn' <= ${mode}_lsn AND state = 'streaming'
          FROM pg_catalog.pg_stat_replication
          WHERE application_name IN ('$standby_name', 'walreceiver')];
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-06 19:49                                                                                                                                                 ` Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-06 19:49 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Mon, Apr 6, 2026 at 10:15 AM Xuneng Zhou <[email protected]> wrote:
>
> On Mon, Apr 6, 2026 at 2:01 PM Xuneng Zhou <[email protected]> wrote:
> >
> > On Mon, Apr 6, 2026 at 11:26 AM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi Alexander,
> > >
> > > On Sun, Apr 5, 2026 at 8:31 PM Alexander Korotkov <[email protected]> wrote:
> > > >
> > > > On Thu, Jan 29, 2026 at 9:47 AM Alexander Korotkov <[email protected]> wrote:
> > > > > On Tue, Jan 27, 2026 at 3:14 AM Xuneng Zhou <[email protected]> wrote:
> > > > > > Heikki spotted a misplaced wake-up call for replay waiters in
> > > > > > PerformWalRecovery. He suggested that the WaitLSNWakeup needs to be
> > > > > > invoked immediately after wal record is applied to avoid the potential
> > > > > > missed wake-ups when recovery stops/pauses/promotes. It makes sense to
> > > > > > me. Please check the attached patch to fix that.
> > > > >
> > > > > Pushed, thank you!
> > > >
> > > > I've assembled small patches, which I think worth pushing before v19 FF.
> > > >
> > > > 1. Avoid syscache lookup in WaitStmtResultDesc().  This is [1] patch,
> > > > but I've removed redundant comment in ExecWaitStmt(), which explained
> > > > the same as WaitStmtResultDesc() comment.
> > > > 2. Use WAIT FOR LSN in wait_for_catchup().  I made the following
> > > > changes: fallback to polling on not_in_recovery result instead of
> > > > croaking, avoid separate pg_is_in_recovery() query, comment why we
> > > > may face the recovery conflict and why it is safe to detect this by
> > > > the error string.
> > > > 3. A paragraph to the docs about possible recovery conflicts and their reason.
> > > >
> > > > I'm going to push this on Monday if no objections.
> > > >
> > > > Links.
> > > > 1. https://www.postgresql.org/message-id/CABPTF7U%2BSUnJX_woQYGe%3D%3DR9Oz%2B-V6X0VO2stBLPGfJmH_LEhw%40...
> > > > 2. https://www.postgresql.org/message-id/CABPTF7X0n%3DR50z2fBpj3EbYYz04Ab0-DHJa%2BJfoAEny62QmUdg%40mail...
> > >
> > > I'll review them shortly.
> > >
> >
> > Here're some comments:
> >
> > 1) Patch 2 checks for not_in_recovery, but WAIT FOR ... NO_THROW
> > returns “not in recovery”, which appears to prevent the
> > promoted-standby fallback described in the patch from triggering.
> > Instead, it seems to result in a hard failure.
> >
> > There are some behavior changes that I overlooked earlier:
> >
> > 2)  Patch 2 changes the helper from "wait until timeout if there is no
> > active replication connection" to "connect to the standby immediately
> > and fail on ordinary connection failures". This appears to differ from
> > the current contract and may affect cases where the standby is down,
> > restarting, or not yet accepting SQL.
> >
> > "If there is no active replication connection from this peer, waits
> > until poll_query_until timeout."
> >
> > 3) In patch 2, we returns success as soon as the standby locally
> > reaches the target LSN, but the existing helper is explicitly defined
> > in terms of pg_stat_replication ... state = 'streaming' on the
> > upstream. So the new path can report success even when there is no
> > active replication connection from that peer anymore.
> >
> > "The replication connection must be in a streaming state."
> >
> > I’m not sure whether these semantic changes are intended.
> >
>
> After some thoughts, these semantic changes appear to be improvements
> over the artifacts of polling-based behavior. The pg_stat_replication
> ... state = 'streaming' is a precondition of polling works rather than
> a post-condition check. The retry timeout also has nothing to do with
> the edge cases and the state of the standby.
>
> In practice, wait_for_catchup() is called when the standby is expected
> to be up. If it's not, one of two things is true:
>
> The standby is briefly restarting -> start() already waits for
> readiness before returning, so this is a non-issue.
> The standby is genuinely down -> waiting 180 seconds to time out
> wastes CI time compared to failing fast.
>
> I’ve addressed point 1 and updated the comments accordingly to reflect
> the new behavior. Please check it.

Thank you, I've pushed your version of patchset.  I made two minor
corrections for patch #2: mention default mode value in the header
comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-07 01:52                                                                                                                                                   ` Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Tom Lane @ 2026-04-07 01:52 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Alexander Korotkov <[email protected]> writes:
> Thank you, I've pushed your version of patchset.  I made two minor
> corrections for patch #2: mention default mode value in the header
> comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.

I wondered why my buildfarm animals got noticeably slower today.
There seem to be a couple of culprits, but one of them is that
7e8aeb9e4 (Use WAIT FOR LSN) has caused the runtime of pg_rewind's
t/003_extrafiles.pl to go through the roof.  On indri's host, that
TAP test took about 3 seconds immediately before that commit, and
about 45 seconds immediately after.  (For scale, the core regression
tests take about 10 seconds on this machine.)  I see roughly
comparable slowdowns on other machines too.

I have not dug into why, nor do I understand why it seems like
only this one test is affected.  In any case, surely this is
unacceptable.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
@ 2026-04-07 02:08                                                                                                                                                     ` Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Tom Lane @ 2026-04-07 02:08 UTC (permalink / raw)
  To: ; +Cc: Alexander Korotkov <[email protected]>; Xuneng Zhou <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

I wrote:
> I wondered why my buildfarm animals got noticeably slower today.
> There seem to be a couple of culprits, but one of them is that
> 7e8aeb9e4 (Use WAIT FOR LSN) has caused the runtime of pg_rewind's
> t/003_extrafiles.pl to go through the roof.  On indri's host, that
> TAP test took about 3 seconds immediately before that commit, and
> about 45 seconds immediately after.

I'm wrong: there's only one culprit.  The other big change in runtime
today is that src/test/recovery's t/033_replay_tsp_drops.pl went from
about 4 seconds to about 46, and that jump also happened at 7e8aeb9e4.
So we still have a mystery, but it's "what do those two tests have in
common that is shared by no others?".

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
@ 2026-04-07 02:28                                                                                                                                                       ` Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-07 02:28 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Tom,

On Tue, Apr 7, 2026 at 10:08 AM Tom Lane <[email protected]> wrote:
>
> I wrote:
> > I wondered why my buildfarm animals got noticeably slower today.
> > There seem to be a couple of culprits, but one of them is that
> > 7e8aeb9e4 (Use WAIT FOR LSN) has caused the runtime of pg_rewind's
> > t/003_extrafiles.pl to go through the roof.  On indri's host, that
> > TAP test took about 3 seconds immediately before that commit, and
> > about 45 seconds immediately after.
>
> I'm wrong: there's only one culprit.  The other big change in runtime
> today is that src/test/recovery's t/033_replay_tsp_drops.pl went from
> about 4 seconds to about 46, and that jump also happened at 7e8aeb9e4.
> So we still have a mystery, but it's "what do those two tests have in
> common that is shared by no others?".
>
>                         regards, tom lane

Thanks for reporting this. I think it could be related to the read of
not-yet-updated writtenUpto position.  I'll look into this and propose
a fix shortly.


--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-07 03:07                                                                                                                                                         ` Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Andres Freund @ 2026-04-07 03:07 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Tom Lane <[email protected]>; Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2026-04-07 10:28:52 +0800, Xuneng Zhou wrote:
> On Tue, Apr 7, 2026 at 10:08 AM Tom Lane <[email protected]> wrote:
> >
> > I wrote:
> > > I wondered why my buildfarm animals got noticeably slower today.
> > > There seem to be a couple of culprits, but one of them is that
> > > 7e8aeb9e4 (Use WAIT FOR LSN) has caused the runtime of pg_rewind's
> > > t/003_extrafiles.pl to go through the roof.  On indri's host, that
> > > TAP test took about 3 seconds immediately before that commit, and
> > > about 45 seconds immediately after.
> >
> > I'm wrong: there's only one culprit.  The other big change in runtime
> > today is that src/test/recovery's t/033_replay_tsp_drops.pl went from
> > about 4 seconds to about 46, and that jump also happened at 7e8aeb9e4.
> > So we still have a mystery, but it's "what do those two tests have in
> > common that is shared by no others?".
> >
> Thanks for reporting this. I think it could be related to the read of
> not-yet-updated writtenUpto position.  I'll look into this and propose
> a fix shortly.

Yes, it's not yet initialized and thus the WAIT FOR waits, even though the
position had already been reached.


But, leaving that aside, looking at this code I'm somewhat concerned - it
seems to not worry at all about memory ordering?


static void
XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
...
	/* Update shared-memory status */
	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);

	/*
	 * If we wrote an LSN that someone was waiting for, notify the waiters.
	 */
	if (waitLSNState &&
		(LogstreamResult.Write >=
		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);

There are no memory barriers here, so the CPU would be entirely free to not
make the writtenUpto write visible to a waiter that's in the process of
registering and is checking whether it needs to wait in WaitForLSN().

And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
loop entry and maybe kinda you can think that WaitLatch() will somehow also
have barrier semantic.  But if so, that would need to be very carefully
documented.  And it seems completely unnecessary here, it's hard to believe
using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
performance issue

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-04-07 03:31                                                                                                                                                           ` Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Andres Freund @ 2026-04-07 03:31 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Tom Lane <[email protected]>; Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2026-04-06 23:07:45 -0400, Andres Freund wrote:
> But, leaving that aside, looking at this code I'm somewhat concerned - it
> seems to not worry at all about memory ordering?
> 
> 
> static void
> XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
> ...
> 	/* Update shared-memory status */
> 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
> 
> 	/*
> 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
> 	 */
> 	if (waitLSNState &&
> 		(LogstreamResult.Write >=
> 		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> 		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> 
> There are no memory barriers here, so the CPU would be entirely free to not
> make the writtenUpto write visible to a waiter that's in the process of
> registering and is checking whether it needs to wait in WaitForLSN().
> 
> And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
> no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
> loop entry and maybe kinda you can think that WaitLatch() will somehow also
> have barrier semantic.  But if so, that would need to be very carefully
> documented.  And it seems completely unnecessary here, it's hard to believe
> using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
> performance issue

And separately from the memory ordering, how can it make sense that there's
at least 5 copies of this

		if (waitLSNState &&
			(LogstreamResult.Flush >=
			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);

around?  That needs to be encapsulated so that if you have a bug, like the
memory ordering problem I describe above, it can be fixed once, not in
multiple places.

And why do these callers even have that pre-check?  Seems WaitLSNWakeup()
does so itself?

	/*
	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
	 * "wake all waiters" (e.g., during promotion when recovery ends).
	 */
	if (XLogRecPtrIsValid(currentLSN) &&
		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
		return;

And why is the code checking if waitLSNState is non-NULL?

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-04-07 04:02                                                                                                                                                             ` Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-07 04:02 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Andres,

On Tue, Apr 7, 2026 at 11:31 AM Andres Freund <[email protected]> wrote:
>
> Hi,
>
> On 2026-04-06 23:07:45 -0400, Andres Freund wrote:
> > But, leaving that aside, looking at this code I'm somewhat concerned - it
> > seems to not worry at all about memory ordering?
> >
> >
> > static void
> > XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
> > ...
> >       /* Update shared-memory status */
> >       pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
> >
> >       /*
> >        * If we wrote an LSN that someone was waiting for, notify the waiters.
> >        */
> >       if (waitLSNState &&
> >               (LogstreamResult.Write >=
> >                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> >               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> >
> > There are no memory barriers here, so the CPU would be entirely free to not
> > make the writtenUpto write visible to a waiter that's in the process of
> > registering and is checking whether it needs to wait in WaitForLSN().
> >
> > And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
> > no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
> > loop entry and maybe kinda you can think that WaitLatch() will somehow also
> > have barrier semantic.  But if so, that would need to be very carefully
> > documented.  And it seems completely unnecessary here, it's hard to believe
> > using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
> > performance issue

Thanks for pointing this out.  This is indeed a store-load ordering issue.

> And separately from the memory ordering, how can it make sense that there's
> at least 5 copies of this
>
>                 if (waitLSNState &&
>                         (LogstreamResult.Flush >=
>                          pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
>                         WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
>
> around?  That needs to be encapsulated so that if you have a bug, like the
> memory ordering problem I describe above, it can be fixed once, not in
> multiple places.

Yeah, this duplication is not ok.

> And why do these callers even have that pre-check?  Seems WaitLSNWakeup()
> does so itself?
>
>         /*
>          * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
>          * "wake all waiters" (e.g., during promotion when recovery ends).
>          */
>         if (XLogRecPtrIsValid(currentLSN) &&
>                 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
>                 return;
>
> And why is the code checking if waitLSNState is non-NULL?
>

These fast checks are unnecessary copy-pastos and waitLSNState checks
also do not make sense except for the one in WaitLSNCleanup.

I'll prepare a patch set addressing them.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-07 12:52                                                                                                                                                               ` Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-07 12:52 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 7, 2026 at 7:03 AM Xuneng Zhou <[email protected]> wrote:
> On Tue, Apr 7, 2026 at 11:31 AM Andres Freund <[email protected]> wrote:
> >
> > Hi,
> >
> > On 2026-04-06 23:07:45 -0400, Andres Freund wrote:
> > > But, leaving that aside, looking at this code I'm somewhat concerned - it
> > > seems to not worry at all about memory ordering?
> > >
> > >
> > > static void
> > > XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
> > > ...
> > >       /* Update shared-memory status */
> > >       pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
> > >
> > >       /*
> > >        * If we wrote an LSN that someone was waiting for, notify the waiters.
> > >        */
> > >       if (waitLSNState &&
> > >               (LogstreamResult.Write >=
> > >                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> > >               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> > >
> > > There are no memory barriers here, so the CPU would be entirely free to not
> > > make the writtenUpto write visible to a waiter that's in the process of
> > > registering and is checking whether it needs to wait in WaitForLSN().
> > >
> > > And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
> > > no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
> > > loop entry and maybe kinda you can think that WaitLatch() will somehow also
> > > have barrier semantic.  But if so, that would need to be very carefully
> > > documented.  And it seems completely unnecessary here, it's hard to believe
> > > using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
> > > performance issue
>
> Thanks for pointing this out.  This is indeed a store-load ordering issue.
>
> > And separately from the memory ordering, how can it make sense that there's
> > at least 5 copies of this
> >
> >                 if (waitLSNState &&
> >                         (LogstreamResult.Flush >=
> >                          pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
> >                         WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
> >
> > around?  That needs to be encapsulated so that if you have a bug, like the
> > memory ordering problem I describe above, it can be fixed once, not in
> > multiple places.
>
> Yeah, this duplication is not ok.
>
> > And why do these callers even have that pre-check?  Seems WaitLSNWakeup()
> > does so itself?
> >
> >         /*
> >          * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> >          * "wake all waiters" (e.g., during promotion when recovery ends).
> >          */
> >         if (XLogRecPtrIsValid(currentLSN) &&
> >                 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
> >                 return;
> >
> > And why is the code checking if waitLSNState is non-NULL?
> >
>
> These fast checks are unnecessary copy-pastos and waitLSNState checks
> also do not make sense except for the one in WaitLSNCleanup.
>
> I'll prepare a patch set addressing them.

Thanks to Tom and Andres for catching these issues!
I'm planning to work on this during this evening.  I'll review
Xuneng's patches (or write my own if they wouldn't arrive yet).

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-07 13:05                                                                                                                                                                 ` Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-07 13:05 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Tue, Apr 7, 2026 at 8:52 PM Alexander Korotkov <[email protected]> wrote:
>
> On Tue, Apr 7, 2026 at 7:03 AM Xuneng Zhou <[email protected]> wrote:
> > On Tue, Apr 7, 2026 at 11:31 AM Andres Freund <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On 2026-04-06 23:07:45 -0400, Andres Freund wrote:
> > > > But, leaving that aside, looking at this code I'm somewhat concerned - it
> > > > seems to not worry at all about memory ordering?
> > > >
> > > >
> > > > static void
> > > > XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
> > > > ...
> > > >       /* Update shared-memory status */
> > > >       pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
> > > >
> > > >       /*
> > > >        * If we wrote an LSN that someone was waiting for, notify the waiters.
> > > >        */
> > > >       if (waitLSNState &&
> > > >               (LogstreamResult.Write >=
> > > >                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> > > >               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> > > >
> > > > There are no memory barriers here, so the CPU would be entirely free to not
> > > > make the writtenUpto write visible to a waiter that's in the process of
> > > > registering and is checking whether it needs to wait in WaitForLSN().
> > > >
> > > > And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
> > > > no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
> > > > loop entry and maybe kinda you can think that WaitLatch() will somehow also
> > > > have barrier semantic.  But if so, that would need to be very carefully
> > > > documented.  And it seems completely unnecessary here, it's hard to believe
> > > > using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
> > > > performance issue
> >
> > Thanks for pointing this out.  This is indeed a store-load ordering issue.
> >
> > > And separately from the memory ordering, how can it make sense that there's
> > > at least 5 copies of this
> > >
> > >                 if (waitLSNState &&
> > >                         (LogstreamResult.Flush >=
> > >                          pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
> > >                         WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
> > >
> > > around?  That needs to be encapsulated so that if you have a bug, like the
> > > memory ordering problem I describe above, it can be fixed once, not in
> > > multiple places.
> >
> > Yeah, this duplication is not ok.
> >
> > > And why do these callers even have that pre-check?  Seems WaitLSNWakeup()
> > > does so itself?
> > >
> > >         /*
> > >          * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> > >          * "wake all waiters" (e.g., during promotion when recovery ends).
> > >          */
> > >         if (XLogRecPtrIsValid(currentLSN) &&
> > >                 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
> > >                 return;
> > >
> > > And why is the code checking if waitLSNState is non-NULL?
> > >
> >
> > These fast checks are unnecessary copy-pastos and waitLSNState checks
> > also do not make sense except for the one in WaitLSNCleanup.
> >
> > I'll prepare a patch set addressing them.
>
> Thanks to Tom and Andres for catching these issues!
> I'm planning to work on this during this evening.  I'll review
> Xuneng's patches (or write my own if they wouldn't arrive yet).
>

I’ve posted two patches. The first fixes the duplication issue
reported by Andres and is fairly straightforward. The second turned
out to be more complex than expected, and I’m still working through
possible solutions. Feedback or alternative approaches would be very
helpful.
I also spent some time drafting a patch to address the memory ordering
issue and will post it later.


-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-07 13:18                                                                                                                                                                   ` Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Andres Freund @ 2026-04-07 13:18 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
> I’ve posted two patches. The first fixes the duplication issue
> reported by Andres and is fairly straightforward. The second turned
> out to be more complex than expected, and I’m still working through
> possible solutions. Feedback or alternative approaches would be very
> helpful.
> I also spent some time drafting a patch to address the memory ordering
> issue and will post it later.

I propose quickly applying a minimal patch like the attached, to get the test
performance back to normal.

Will do so unless somebody protests within in one CI cycle and one coffee.

Greetings,

Andres Freund


^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-04-07 13:46                                                                                                                                                                     ` Alexander Korotkov <[email protected]>
  2026-04-07 13:52                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 15:55                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-07 13:46 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 7, 2026, 16:18 Andres Freund <[email protected]> wrote:

> Hi,
>
> On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
> > I’ve posted two patches. The first fixes the duplication issue
> > reported by Andres and is fairly straightforward. The second turned
> > out to be more complex than expected, and I’m still working through
> > possible solutions. Feedback or alternative approaches would be very
> > helpful.
> > I also spent some time drafting a patch to address the memory ordering
> > issue and will post it later.
>
> I propose quickly applying a minimal patch like the attached, to get the
> test
> performance back to normal.
>
> Will do so unless somebody protests within in one CI cycle and one coffee.
>

I would be able to review them only after several hours. But +1 for
applying now to get rid of buildfarm slowdown.

------
Regards,
Alexander Korotkov


^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-07 13:52                                                                                                                                                                       ` Andres Freund <[email protected]>
  2026-04-07 14:29                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Andres Freund @ 2026-04-07 13:52 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2026-04-07 16:46:29 +0300, Alexander Korotkov wrote:
> On Tue, Apr 7, 2026, 16:18 Andres Freund <[email protected]> wrote:
> > On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
> > > I’ve posted two patches. The first fixes the duplication issue
> > > reported by Andres and is fairly straightforward. The second turned
> > > out to be more complex than expected, and I’m still working through
> > > possible solutions. Feedback or alternative approaches would be very
> > > helpful.
> > > I also spent some time drafting a patch to address the memory ordering
> > > issue and will post it later.
> >
> > I propose quickly applying a minimal patch like the attached, to get the
> > test
> > performance back to normal.
> >
> > Will do so unless somebody protests within in one CI cycle and one coffee.
> >
> 
> I would be able to review them only after several hours. But +1 for
> applying now to get rid of buildfarm slowdown.

Done.

Just to be clear, I didn't apply Xuneng's patches, just the minimal fix for
the slowdown.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:52                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-04-07 14:29                                                                                                                                                                         ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-07 14:29 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 7, 2026 at 9:52 PM Andres Freund <[email protected]> wrote:
>
> Hi,
>
> On 2026-04-07 16:46:29 +0300, Alexander Korotkov wrote:
> > On Tue, Apr 7, 2026, 16:18 Andres Freund <[email protected]> wrote:
> > > On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
> > > > I’ve posted two patches. The first fixes the duplication issue
> > > > reported by Andres and is fairly straightforward. The second turned
> > > > out to be more complex than expected, and I’m still working through
> > > > possible solutions. Feedback or alternative approaches would be very
> > > > helpful.
> > > > I also spent some time drafting a patch to address the memory ordering
> > > > issue and will post it later.
> > >
> > > I propose quickly applying a minimal patch like the attached, to get the
> > > test
> > > performance back to normal.
> > >
> > > Will do so unless somebody protests within in one CI cycle and one coffee.
> > >
> >
> > I would be able to review them only after several hours. But +1 for
> > applying now to get rid of buildfarm slowdown.
>
> Done.

Thanks for dealing with it!

> Just to be clear, I didn't apply Xuneng's patches, just the minimal fix for
> the slowdown.
>

Ok, I don’t think that patch is in a committable state; posting it for
reference.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-07 15:55                                                                                                                                                                       ` Xuneng Zhou <[email protected]>
  2026-04-07 23:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-07 15:55 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 7, 2026 at 9:46 PM Alexander Korotkov <[email protected]> wrote:
>
> On Tue, Apr 7, 2026, 16:18 Andres Freund <[email protected]> wrote:
>>
>> Hi,
>>
>> On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
>> > I’ve posted two patches. The first fixes the duplication issue
>> > reported by Andres and is fairly straightforward. The second turned
>> > out to be more complex than expected, and I’m still working through
>> > possible solutions. Feedback or alternative approaches would be very
>> > helpful.
>> > I also spent some time drafting a patch to address the memory ordering
>> > issue and will post it later.
>>
>> I propose quickly applying a minimal patch like the attached, to get the test
>> performance back to normal.
>>
>> Will do so unless somebody protests within in one CI cycle and one coffee.
>
>
> I would be able to review them only after several hours. But +1 for applying now to get rid of buildfarm slowdown.

Here is a patch addressing the memory order issue reported earlier.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (2.7K, ../../CABPTF7Wjk_FbOghyr09Rzu6T2bh-L_KBMqHK+zhRXpssU0STyQ@mail.gmail.com/2-v1-0001-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From fe98204fe9fb0773a66388411c481bede6063b5b Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 7 Apr 2026 23:37:51 +0800
Subject: [PATCH v1] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an
LSN position then reads minWaitedLSN; the waiter stores its
target into minWaitedLSN then reads the position.  Without a
barrier between each side's store and load, the CPU may satisfy
the load before the store becomes globally visible, causing the
waker to miss a just-registered waiter and the waiter to miss
a just-advanced position.  The result is a missed wakeup: the
waiter sleeps indefinitely until the next unrelated event.

Fix by adding pg_memory_barrier() in WaitLSNWakeup() before
reading minWaitedLSN (waker side) and in
GetCurrentLSNForWaitType() before reading the current position
(waiter side).  The waiter side was previously covered only by
the implicit barrier in LWLockRelease()'s atomic RMW; make this
explicit.  Subsequent loop iterations are safe because
WaitLatch()/ResetLatch() provide the necessary ordering.

Reported-by: Andres Freund
---
 src/backend/access/transam/xlogwait.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..559cad0ad00 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -99,6 +99,14 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * Ensure our minWaitedLSN publication from addLSNWaiter() is globally
+	 * visible before reading the current position. This keeps the waiter-side
+	 * ordering explicit within the WAIT FOR LSN handshake instead of relying
+	 * on the preceding LWLockRelease() path.
+	 */
+	pg_memory_barrier();
+
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -323,6 +331,18 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * Ensure the waker's prior position store (writtenUpto, flushedUpto,
+	 * lastReplayedEndRecPtr, etc.) is globally visible before we read
+	 * minWaitedLSN.  Without this barrier, the CPU could load minWaitedLSN
+	 * before draining the position store, leaving the position invisible to a
+	 * concurrently-registering waiter.
+	 *
+	 * This is the waker side of a Dekker-style handshake; pairs with
+	 * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side.
+	 */
+	pg_memory_barrier();
+
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
 	 * "wake all waiters" (e.g., during promotion when recovery ends).
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 15:55                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-07 23:30                                                                                                                                                                         ` Alexander Korotkov <[email protected]>
  2026-04-08 00:20                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 00:50                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-07 23:30 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 7, 2026 at 6:55 PM Xuneng Zhou <[email protected]> wrote:
>
> On Tue, Apr 7, 2026 at 9:46 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Tue, Apr 7, 2026, 16:18 Andres Freund <[email protected]> wrote:
> >>
> >> Hi,
> >>
> >> On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
> >> > I’ve posted two patches. The first fixes the duplication issue
> >> > reported by Andres and is fairly straightforward. The second turned
> >> > out to be more complex than expected, and I’m still working through
> >> > possible solutions. Feedback or alternative approaches would be very
> >> > helpful.
> >> > I also spent some time drafting a patch to address the memory ordering
> >> > issue and will post it later.
> >>
> >> I propose quickly applying a minimal patch like the attached, to get the test
> >> performance back to normal.
> >>
> >> Will do so unless somebody protests within in one CI cycle and one coffee.
> >
> >
> > I would be able to review them only after several hours. But +1 for applying now to get rid of buildfarm slowdown.
>
> Here is a patch addressing the memory order issue reported earlier.

I agree to change in WaitLSNWakeup(), memory barrier looks necessary there.
Regarding GetCurrentLSNForWaitType(), I don't think barrier is needed
here, nor think it makes things clearer.  I think it would be enough
to comment that LWLock operations in addLSNWaiter()/deleteLSNWaiter()
provide necessary barriers.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 15:55                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-08 00:20                                                                                                                                                                           ` Xuneng Zhou <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-08 00:20 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Apr 8, 2026 at 7:30 AM Alexander Korotkov <[email protected]> wrote:
>
> On Tue, Apr 7, 2026 at 6:55 PM Xuneng Zhou <[email protected]> wrote:
> >
> > On Tue, Apr 7, 2026 at 9:46 PM Alexander Korotkov <[email protected]> wrote:
> > >
> > > On Tue, Apr 7, 2026, 16:18 Andres Freund <[email protected]> wrote:
> > >>
> > >> Hi,
> > >>
> > >> On 2026-04-07 21:05:40 +0800, Xuneng Zhou wrote:
> > >> > I’ve posted two patches. The first fixes the duplication issue
> > >> > reported by Andres and is fairly straightforward. The second turned
> > >> > out to be more complex than expected, and I’m still working through
> > >> > possible solutions. Feedback or alternative approaches would be very
> > >> > helpful.
> > >> > I also spent some time drafting a patch to address the memory ordering
> > >> > issue and will post it later.
> > >>
> > >> I propose quickly applying a minimal patch like the attached, to get the test
> > >> performance back to normal.
> > >>
> > >> Will do so unless somebody protests within in one CI cycle and one coffee.
> > >
> > >
> > > I would be able to review them only after several hours. But +1 for applying now to get rid of buildfarm slowdown.
> >
> > Here is a patch addressing the memory order issue reported earlier.
>
> I agree to change in WaitLSNWakeup(), memory barrier looks necessary there.
> Regarding GetCurrentLSNForWaitType(), I don't think barrier is needed
> here, nor think it makes things clearer.  I think it would be enough
> to comment that LWLock operations in addLSNWaiter()/deleteLSNWaiter()
> provide necessary barriers.
>

I’m fine with that if Andres has no objection. That said, callers of
WaitLSNWakeup except STANDBY_WRITE do acquire locks before the wakeup.
I’m not sure whether a memory barrier is required for all of them,
though placing the barrier inside WaitLSNWakeup would make the
handling less scattered.

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 15:55                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-08 00:50                                                                                                                                                                           ` Andres Freund <[email protected]>
  2026-04-08 07:31                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Andres Freund @ 2026-04-08 00:50 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On 2026-04-08 02:30:44 +0300, Alexander Korotkov wrote:
> On Tue, Apr 7, 2026 at 6:55 PM Xuneng Zhou <[email protected]> wrote:
> I agree to change in WaitLSNWakeup(), memory barrier looks necessary there.
> Regarding GetCurrentLSNForWaitType(), I don't think barrier is needed
> here, nor think it makes things clearer.  I think it would be enough
> to comment that LWLock operations in addLSNWaiter()/deleteLSNWaiter()
> provide necessary barriers.

That's sufficient for the first iteration, but what guarantees it once you do
WaitLatch()?  That's likely going to imply a barrier somewhere in the kernel,
but I don't think there's any actual guarantee.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 15:55                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 00:50                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-04-08 07:31                                                                                                                                                                             ` Alexander Korotkov <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-08 07:31 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Apr 8, 2026 at 3:50 AM Andres Freund <[email protected]> wrote:
> On 2026-04-08 02:30:44 +0300, Alexander Korotkov wrote:
> > On Tue, Apr 7, 2026 at 6:55 PM Xuneng Zhou <[email protected]> wrote:
> > I agree to change in WaitLSNWakeup(), memory barrier looks necessary there.
> > Regarding GetCurrentLSNForWaitType(), I don't think barrier is needed
> > here, nor think it makes things clearer.  I think it would be enough
> > to comment that LWLock operations in addLSNWaiter()/deleteLSNWaiter()
> > provide necessary barriers.
>
> That's sufficient for the first iteration, but what guarantees it once you do
> WaitLatch()?  That's likely going to imply a barrier somewhere in the kernel,
> but I don't think there's any actual guarantee.

After WaitLatch(), ResetLatch() contains memory barrier.  And as I
understand, this memory barrier includes guarantees for reading fresh
values after WaitLatch() in typical latch usage scenario.  However, I
see in WaitForLSN() we can exit from WaitLatch() on timeout, and then
potentially exit from loop on timeout without rechecking for the most
fresh LSN.  I suppose we can just do ResetLatch() unconditionally to
fix that.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-07 12:59                                                                                                                                                               ` Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-07 12:59 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 7, 2026 at 12:02 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi Andres,
>
> On Tue, Apr 7, 2026 at 11:31 AM Andres Freund <[email protected]> wrote:
> >
> > Hi,
> >
> > On 2026-04-06 23:07:45 -0400, Andres Freund wrote:
> > > But, leaving that aside, looking at this code I'm somewhat concerned - it
> > > seems to not worry at all about memory ordering?
> > >
> > >
> > > static void
> > > XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
> > > ...
> > >       /* Update shared-memory status */
> > >       pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
> > >
> > >       /*
> > >        * If we wrote an LSN that someone was waiting for, notify the waiters.
> > >        */
> > >       if (waitLSNState &&
> > >               (LogstreamResult.Write >=
> > >                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> > >               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> > >
> > > There are no memory barriers here, so the CPU would be entirely free to not
> > > make the writtenUpto write visible to a waiter that's in the process of
> > > registering and is checking whether it needs to wait in WaitForLSN().
> > >
> > > And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
> > > no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
> > > loop entry and maybe kinda you can think that WaitLatch() will somehow also
> > > have barrier semantic.  But if so, that would need to be very carefully
> > > documented.  And it seems completely unnecessary here, it's hard to believe
> > > using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
> > > performance issue
>
> Thanks for pointing this out.  This is indeed a store-load ordering issue.
>
> > And separately from the memory ordering, how can it make sense that there's
> > at least 5 copies of this
> >
> >                 if (waitLSNState &&
> >                         (LogstreamResult.Flush >=
> >                          pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
> >                         WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
> >
> > around?  That needs to be encapsulated so that if you have a bug, like the
> > memory ordering problem I describe above, it can be fixed once, not in
> > multiple places.
>
> Yeah, this duplication is not ok.
>
> > And why do these callers even have that pre-check?  Seems WaitLSNWakeup()
> > does so itself?
> >
> >         /*
> >          * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> >          * "wake all waiters" (e.g., during promotion when recovery ends).
> >          */
> >         if (XLogRecPtrIsValid(currentLSN) &&
> >                 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
> >                 return;
> >
> > And why is the code checking if waitLSNState is non-NULL?
> >
>
> These fast checks are unnecessary copy-pastos and waitLSNState checks
> also do not make sense except for the one in WaitLSNCleanup.
>
> I'll prepare a patch set addressing them.
>

Here is some analysis of the issue reported by Tom:

1) The problem

WAIT FOR LSN with standby_write or standby_flush mode can block
indefinitely on an idle primary even when the target LSN is already
satisfied by WAL on disk.

The walreceiver initializes its process-local LogstreamResult.Write
and LogstreamResult.Flush from GetXLogReplayRecPtr() at connect time,
reflecting all WAL already present on the standby (from a base backup,
archive restore, or prior streaming). The shared-memory positions used
by WAIT FOR LSN, however, are not seeded from this value:

WalRcv->writtenUpto is zero-initialized by ShmemInitStruct and remains
0 until XLogWalRcvWrite() processes incoming streaming data.
WalRcv->flushedUpto is initialized to the segment-aligned streaming
start point by RequestXLogStreaming(), which may be significantly
behind the replay position. It advances only when XLogWalRcvFlush()
processes new data — which itself requires LogstreamResult.Flush <
LogstreamResult.Write, a condition that never holds at startup since
both fields are initialized to the same value.

When the primary is idle and sends no new WAL, both positions stay at
their initial stale values indefinitely.

2) The fix
Seed writtenUpto and flushedUpto from LogstreamResult immediately
after the walreceiver initializes those process-local fields, then
call WaitLSNWakeup() to wake any already-blocked waiters.

This broadens the semantics of these fields. writtenUpto and
flushedUpto  used to track only WAL written or flushed by the current
walreceiver session — WAL received from the primary since the most
recent connect. After this change, they are initialized to the replay
position, so they also cover WAL that was already on disk before
streaming began. This affects pg_stat_wal_receiver.written_lsn and
flushed_lsn, which will now report the replay position immediately at
walreceiver startup rather than 0 and the segment boundary
respectively. I am still considering whether this semantic change is
acceptable though it does shorten the runtime of the tap tests
reported by Tom in my test. Another approach is to modify the logic of
GetCurrentLSNForWaitType to cope with this special case and leave the
publisher side alone without changing the semantics. But this seems to
be more subtle.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.0K, ../../CABPTF7WAcV_uEaJa3jmYPc26rApa64hV5HmjRBN=OS17MAybUg@mail.gmail.com/2-v1-0001-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From b93a6e64cf2e9afcfa7ee4dee7c6dfab885a0585 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 7 Apr 2026 18:31:36 +0800
Subject: [PATCH v1 1/2] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 260fc801ce2..dd85bf52dc8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index a437273cf9a..c7dcb3003b5 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -980,12 +980,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1027,13 +1025,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.51.0



  [application/octet-stream] v1-0002-Fix-WAIT-FOR-LSN-standby_write-standby_flush-hang.patch (3.3K, ../../CABPTF7WAcV_uEaJa3jmYPc26rApa64hV5HmjRBN=OS17MAybUg@mail.gmail.com/3-v1-0002-Fix-WAIT-FOR-LSN-standby_write-standby_flush-hang.patch)
  download | inline diff:
From e542061b02f154c4c81a989b239a71b02dc09638 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 7 Apr 2026 20:18:26 +0800
Subject: [PATCH v1 2/2] Fix WAIT FOR LSN standby_write/standby_flush hang on
 idle primary

After walreceiver connects, WalRcv->writtenUpto remains at 0 until
new streaming data arrives.  If the target LSN is already on disk
from a base backup or archive restore and the primary is idle, WAIT
FOR LSN with standby_write or standby_flush mode blocks indefinitely.

Fix by having GetCurrentLSNForWaitType() fall back to the replay
position when the walreceiver hasn't written any WAL yet.  WAL up
to the replay point is already on disk and should satisfy the wait.
Additionally, wake any already-blocked waiters at walreceiver
startup so they re-check their condition through the new fallback.

Reported-by: Tom Lane
---
 src/backend/access/transam/xlogwait.c | 24 ++++++++++++++++++++++--
 src/backend/replication/walreceiver.c | 11 +++++++++++
 2 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..7b102a16fc4 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -105,10 +105,30 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+
+				/*
+				 * If the walreceiver hasn't written any WAL yet, fall back to
+				 * the replay position.  WAL up to the replay point is already
+				 * on disk (from base backup, archive restore, or prior
+				 * streaming), so there is no reason to wait for the
+				 * walreceiver to re-receive it.
+				 */
+				if (recptr == InvalidXLogRecPtr)
+					recptr = GetXLogReplayRecPtr(NULL);
+				return recptr;
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+
+				/* Same fallback as standby_write; see comment above. */
+				if (recptr == InvalidXLogRecPtr)
+					recptr = GetXLogReplayRecPtr(NULL);
+				return recptr;
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c7dcb3003b5..0cb734cd2fc 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -411,6 +411,17 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 			LogstreamResult.Write = LogstreamResult.Flush = GetXLogReplayRecPtr(NULL);
 			initStringInfo(&reply_message);
 
+			/*
+			 * Wake any already-blocked standby_write or standby_flush
+			 * waiters. Their target LSN may already be satisfied by WAL on
+			 * disk from a base backup or archive restore. We don't seed the
+			 * shared-memory positions here; instead,
+			 * GetCurrentLSNForWaitType() falls back to the replay position
+			 * when the walreceiver hasn't written anything yet.
+			 */
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+
 			/* Initialize nap wakeup times. */
 			now = GetCurrentTimestamp();
 			for (int i = 0; i < NUM_WALRCV_WAKEUPS; ++i)
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-07 23:23                                                                                                                                                                 ` Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-07 23:23 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi, Xuneng!

On Tue, Apr 7, 2026 at 4:00 PM Xuneng Zhou <[email protected]> wrote:
>
> On Tue, Apr 7, 2026 at 12:02 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi Andres,
> >
> > On Tue, Apr 7, 2026 at 11:31 AM Andres Freund <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On 2026-04-06 23:07:45 -0400, Andres Freund wrote:
> > > > But, leaving that aside, looking at this code I'm somewhat concerned - it
> > > > seems to not worry at all about memory ordering?
> > > >
> > > >
> > > > static void
> > > > XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
> > > > ...
> > > >       /* Update shared-memory status */
> > > >       pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
> > > >
> > > >       /*
> > > >        * If we wrote an LSN that someone was waiting for, notify the waiters.
> > > >        */
> > > >       if (waitLSNState &&
> > > >               (LogstreamResult.Write >=
> > > >                pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
> > > >               WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
> > > >
> > > > There are no memory barriers here, so the CPU would be entirely free to not
> > > > make the writtenUpto write visible to a waiter that's in the process of
> > > > registering and is checking whether it needs to wait in WaitForLSN().
> > > >
> > > > And WaitForLSN()->GetCurrentLSNForWaitType()->GetWalRcvWriteRecPtr() also has
> > > > no barriers.  That MAYBE is ok, due addLSNWaiter() providing the barrier at
> > > > loop entry and maybe kinda you can think that WaitLatch() will somehow also
> > > > have barrier semantic.  But if so, that would need to be very carefully
> > > > documented.  And it seems completely unnecessary here, it's hard to believe
> > > > using a barrier (via pg_atomic_read_membarrier_u64() or such) would be a
> > > > performance issue
> >
> > Thanks for pointing this out.  This is indeed a store-load ordering issue.
> >
> > > And separately from the memory ordering, how can it make sense that there's
> > > at least 5 copies of this
> > >
> > >                 if (waitLSNState &&
> > >                         (LogstreamResult.Flush >=
> > >                          pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
> > >                         WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
> > >
> > > around?  That needs to be encapsulated so that if you have a bug, like the
> > > memory ordering problem I describe above, it can be fixed once, not in
> > > multiple places.
> >
> > Yeah, this duplication is not ok.
> >
> > > And why do these callers even have that pre-check?  Seems WaitLSNWakeup()
> > > does so itself?
> > >
> > >         /*
> > >          * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> > >          * "wake all waiters" (e.g., during promotion when recovery ends).
> > >          */
> > >         if (XLogRecPtrIsValid(currentLSN) &&
> > >                 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
> > >                 return;
> > >
> > > And why is the code checking if waitLSNState is non-NULL?
> > >
> >
> > These fast checks are unnecessary copy-pastos and waitLSNState checks
> > also do not make sense except for the one in WaitLSNCleanup.
> >
> > I'll prepare a patch set addressing them.
> >
>
> Here is some analysis of the issue reported by Tom:
>
> 1) The problem
>
> WAIT FOR LSN with standby_write or standby_flush mode can block
> indefinitely on an idle primary even when the target LSN is already
> satisfied by WAL on disk.
>
> The walreceiver initializes its process-local LogstreamResult.Write
> and LogstreamResult.Flush from GetXLogReplayRecPtr() at connect time,
> reflecting all WAL already present on the standby (from a base backup,
> archive restore, or prior streaming). The shared-memory positions used
> by WAIT FOR LSN, however, are not seeded from this value:
>
> WalRcv->writtenUpto is zero-initialized by ShmemInitStruct and remains
> 0 until XLogWalRcvWrite() processes incoming streaming data.
> WalRcv->flushedUpto is initialized to the segment-aligned streaming
> start point by RequestXLogStreaming(), which may be significantly
> behind the replay position. It advances only when XLogWalRcvFlush()
> processes new data — which itself requires LogstreamResult.Flush <
> LogstreamResult.Write, a condition that never holds at startup since
> both fields are initialized to the same value.
>
> When the primary is idle and sends no new WAL, both positions stay at
> their initial stale values indefinitely.
>
> 2) The fix
> Seed writtenUpto and flushedUpto from LogstreamResult immediately
> after the walreceiver initializes those process-local fields, then
> call WaitLSNWakeup() to wake any already-blocked waiters.
>
> This broadens the semantics of these fields. writtenUpto and
> flushedUpto  used to track only WAL written or flushed by the current
> walreceiver session — WAL received from the primary since the most
> recent connect. After this change, they are initialized to the replay
> position, so they also cover WAL that was already on disk before
> streaming began. This affects pg_stat_wal_receiver.written_lsn and
> flushed_lsn, which will now report the replay position immediately at
> walreceiver startup rather than 0 and the segment boundary
> respectively. I am still considering whether this semantic change is
> acceptable though it does shorten the runtime of the tap tests
> reported by Tom in my test. Another approach is to modify the logic of
> GetCurrentLSNForWaitType to cope with this special case and leave the
> publisher side alone without changing the semantics. But this seems to
> be more subtle.

Patch 0001 looks OK for me.
Regarding patch 0002.  Changes made for GetCurrentLSNForWaitType()
looks reliable for me.  PerformWalRecovery() sets replayed positions
before starting recovery, and in turn before standby can accept
connections.  So, changes to WalReceiverMain() don't look necessary to
me.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-08 03:23                                                                                                                                                                   ` Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-08 03:23 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Apr 8, 2026 at 7:23 AM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> > Here is some analysis of the issue reported by Tom:
> >
> > 1) The problem
> >
> > WAIT FOR LSN with standby_write or standby_flush mode can block
> > indefinitely on an idle primary even when the target LSN is already
> > satisfied by WAL on disk.
> >
> > The walreceiver initializes its process-local LogstreamResult.Write
> > and LogstreamResult.Flush from GetXLogReplayRecPtr() at connect time,
> > reflecting all WAL already present on the standby (from a base backup,
> > archive restore, or prior streaming). The shared-memory positions used
> > by WAIT FOR LSN, however, are not seeded from this value:
> >
> > WalRcv->writtenUpto is zero-initialized by ShmemInitStruct and remains
> > 0 until XLogWalRcvWrite() processes incoming streaming data.
> > WalRcv->flushedUpto is initialized to the segment-aligned streaming
> > start point by RequestXLogStreaming(), which may be significantly
> > behind the replay position. It advances only when XLogWalRcvFlush()
> > processes new data — which itself requires LogstreamResult.Flush <
> > LogstreamResult.Write, a condition that never holds at startup since
> > both fields are initialized to the same value.
> >
> > When the primary is idle and sends no new WAL, both positions stay at
> > their initial stale values indefinitely.
> >
> > 2) The fix
> > Seed writtenUpto and flushedUpto from LogstreamResult immediately
> > after the walreceiver initializes those process-local fields, then
> > call WaitLSNWakeup() to wake any already-blocked waiters.
> >
> > This broadens the semantics of these fields. writtenUpto and
> > flushedUpto  used to track only WAL written or flushed by the current
> > walreceiver session — WAL received from the primary since the most
> > recent connect. After this change, they are initialized to the replay
> > position, so they also cover WAL that was already on disk before
> > streaming began. This affects pg_stat_wal_receiver.written_lsn and
> > flushed_lsn, which will now report the replay position immediately at
> > walreceiver startup rather than 0 and the segment boundary
> > respectively. I am still considering whether this semantic change is
> > acceptable though it does shorten the runtime of the tap tests
> > reported by Tom in my test. Another approach is to modify the logic of
> > GetCurrentLSNForWaitType to cope with this special case and leave the
> > publisher side alone without changing the semantics. But this seems to
> > be more subtle.
>
> Patch 0001 looks OK for me.
> Regarding patch 0002.  Changes made for GetCurrentLSNForWaitType()
> looks reliable for me.  PerformWalRecovery() sets replayed positions
> before starting recovery, and in turn before standby can accept
> connections.  So, changes to WalReceiverMain() don't look necessary to
> me.

Yeah, GetCurrentLSNForWaitType seems to be the right place to place
the fix. Please see the attached patch 2.

I also noticed another relevent problem:

During pure archive recovery (no walreceiver), a backend that issues
'WAIT FOR LSN ... MODE 'standby_write' with a target ahead of the
current replay position will sleep forever; the startup process
replays past the target but only wakes 'STANDBY_REPLAY' waiters.

This also affects mixed scenarios: the walreceiver may lag behind
replay (e.g., archive restore has delivered WAL faster than
streaming), so a 'standby_write' waiter could be waiting on WAL that
replay has already consumed.

I will write a patch to address this soon.

--
Best,
Xuneng


Attachments:

  [application/x-patch] v1-0001-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.0K, ../../CABPTF7X0iV=kGC4gjsTj4NvK_NNEJGM3YTc7Obxs5GOiYoMhEw@mail.gmail.com/2-v1-0001-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From b93a6e64cf2e9afcfa7ee4dee7c6dfab885a0585 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 7 Apr 2026 18:31:36 +0800
Subject: [PATCH v1 1/2] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 260fc801ce2..dd85bf52dc8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index a437273cf9a..c7dcb3003b5 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -980,12 +980,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1027,13 +1025,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.51.0



  [application/octet-stream] v1-0002-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch (8.6K, ../../CABPTF7X0iV=kGC4gjsTj4NvK_NNEJGM3YTc7Obxs5GOiYoMhEw@mail.gmail.com/3-v1-0002-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch)
  download | inline diff:
From 467d576c1ffde48d6c21ecd4619b8b059a291dc5 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 8 Apr 2026 09:11:14 +0800
Subject: [PATCH v1 2/2] Use replay position as floor for WAIT FOR LSN
 standby_write/standby_flush

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index c30fba6f05a..d4d66b0e1f2 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6f4b5486b93 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -105,10 +105,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index bf61b8c47cf..8e8776f3ea4 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -652,4 +652,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-08 04:59                                                                                                                                                                     ` Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-08 04:59 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

> > Patch 0001 looks OK for me.
> > Regarding patch 0002.  Changes made for GetCurrentLSNForWaitType()
> > looks reliable for me.  PerformWalRecovery() sets replayed positions
> > before starting recovery, and in turn before standby can accept
> > connections.  So, changes to WalReceiverMain() don't look necessary to
> > me.
>
> Yeah, GetCurrentLSNForWaitType seems to be the right place to place
> the fix. Please see the attached patch 2.
>
> I also noticed another relevent problem:
>
> During pure archive recovery (no walreceiver), a backend that issues
> 'WAIT FOR LSN ... MODE 'standby_write' with a target ahead of the
> current replay position will sleep forever; the startup process
> replays past the target but only wakes 'STANDBY_REPLAY' waiters.
>
> This also affects mixed scenarios: the walreceiver may lag behind
> replay (e.g., archive restore has delivered WAL faster than
> streaming), so a 'standby_write' waiter could be waiting on WAL that
> replay has already consumed.
>
> I will write a patch to address this soon.
>

Here is the patch.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0003-Wake-standby_write-standby_flush-waiters-from-the.patch (5.9K, ../../CABPTF7UBdEfyxATWntmCfoJrwB6iPrnhkXO7y_Avmqc2bOn27A@mail.gmail.com/2-v1-0003-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From 50cce79dd5eca180c3bd3b0e098a8e4991b6b8ce Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 8 Apr 2026 12:48:54 +0800
Subject: [PATCH v1 3/3] Wake standby_write/standby_flush waiters from the WAL
 replay loop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 8e8776f3ea4..8f623c475db 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -668,6 +668,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -722,6 +733,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-09 15:21                                                                                                                                                                       ` Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-15 08:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-09 15:21 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Apr 8, 2026 at 7:59 AM Xuneng Zhou <[email protected]> wrote:
> > > Patch 0001 looks OK for me.
> > > Regarding patch 0002.  Changes made for GetCurrentLSNForWaitType()
> > > looks reliable for me.  PerformWalRecovery() sets replayed positions
> > > before starting recovery, and in turn before standby can accept
> > > connections.  So, changes to WalReceiverMain() don't look necessary to
> > > me.
> >
> > Yeah, GetCurrentLSNForWaitType seems to be the right place to place
> > the fix. Please see the attached patch 2.
> >
> > I also noticed another relevent problem:
> >
> > During pure archive recovery (no walreceiver), a backend that issues
> > 'WAIT FOR LSN ... MODE 'standby_write' with a target ahead of the
> > current replay position will sleep forever; the startup process
> > replays past the target but only wakes 'STANDBY_REPLAY' waiters.
> >
> > This also affects mixed scenarios: the walreceiver may lag behind
> > replay (e.g., archive restore has delivered WAL faster than
> > streaming), so a 'standby_write' waiter could be waiting on WAL that
> > replay has already consumed.
> >
> > I will write a patch to address this soon.
> >
>
> Here is the patch.

I've assembled all the pending patches together.
0001 adds memory barrier to GetWalRcvWriteRecPtr() as suggested by
Andres off-list.
0002 is basically [1] by Xuneng, but revised given we have a memory
barrier in 0001, and my proposal to do ResetLatch() unconditionally
similar to our other Latch-based loops.
0003 and 0004 are [2] by Xuneng.
0005 is [3] by Xuneng.

I'm going to add them to Commitfest to run CI over them, and have a
closer look over them tomorrow.


Links.
1. https://www.postgresql.org/message-id/CABPTF7Wjk_FbOghyr09Rzu6T2bh-L_KBMqHK%2BzhRXpssU0STyQ%40mail.g...
2. https://www.postgresql.org/message-id/CABPTF7X0iV%3DkGC4gjsTj4NvK_NNEJGM3YTc7Obxs5GOiYoMhEw%40mail.g...
3. https://www.postgresql.org/message-id/CABPTF7UBdEfyxATWntmCfoJrwB6iPrnhkXO7y_Avmqc2bOn27A%40mail.gma...

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v3-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (3.2K, ../../CAPpHfduhQsm44j_ziZ6ykFTDZ2SFvZp04iCrc5_dD9PGZUrhrw@mail.gmail.com/2-v3-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From 31f4549c1f271563eb9191478e6766c0848bde07 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Thu, 9 Apr 2026 18:08:06 +0300
Subject: [PATCH v3 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN position
then reads minWaitedLSN; the waiter stores its target into minWaitedLSN then
reads the position.  Without a barrier between each side's store and load,
the CPU may satisfy the load before the store becomes globally visible,
causing the waker to miss a just-registered waiter and the waiter to miss
a just-advanced position.  The result is a missed wakeup: the waiter sleeps
indefinitely until the next unrelated event.

Fix by adding pg_memory_barrier() in WaitLSNWakeup() before
reading minWaitedLSN (waker side) and explain that GetCurrentLSNForWaitType()
now has the memory barrier before reading the current position (waiter side).

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..5be84bd4566 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provides memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -323,6 +329,18 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * Ensure the waker's prior position store (writtenUpto, flushedUpto,
+	 * lastReplayedEndRecPtr, etc.) is globally visible before we read
+	 * minWaitedLSN.  Without this barrier, the CPU could load minWaitedLSN
+	 * before draining the position store, leaving the position invisible to a
+	 * concurrently-registering waiter.
+	 *
+	 * This is the waker side of a Dekker-style handshake; pairs with
+	 * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side.
+	 */
+	pg_memory_barrier();
+
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
 	 * "wake all waiters" (e.g., during promotion when recovery ends).
@@ -450,8 +468,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0001-Add-a-memory-barrier-to-GetWalRcvWriteRecPtr.patch (1.8K, ../../CAPpHfduhQsm44j_ziZ6ykFTDZ2SFvZp04iCrc5_dD9PGZUrhrw@mail.gmail.com/3-v3-0001-Add-a-memory-barrier-to-GetWalRcvWriteRecPtr.patch)
  download | inline diff:
From 0e5b4d1b9311a628a70218d89abf12308c9d782f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Thu, 9 Apr 2026 16:49:04 +0300
Subject: [PATCH v3 1/5] Add a memory barrier to GetWalRcvWriteRecPtr()

Add pg_memory_barrier() before reading writtenUpto so that callers see
up-to-date shared memory state.  This matches the barrier semantics that
GetWalRcvFlushRecPtr() and other LSN-position functions get implicitly from
their spinlock acquire/release, and in turn protects from bugs caused by
expectations of similar barrier guarantees from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiverfuncs.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..0408ddff43e 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -363,14 +363,22 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use a memory barrier to ensure that callers see up-to-date shared memory
+ * state, matching the barrier semantics provided by the spinlock in
+ * GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
+	XLogRecPtr	recptr;
+
+	pg_memory_barrier();
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	recptr = pg_atomic_read_u64(&walrcv->writtenUpto);
+
+	return recptr;
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch (8.7K, ../../CAPpHfduhQsm44j_ziZ6ykFTDZ2SFvZp04iCrc5_dD9PGZUrhrw@mail.gmail.com/4-v3-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch)
  download | inline diff:
From e9b8cd851b754dd3f8d44955b6652d6058fbee7b Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Wed, 8 Apr 2026 09:11:14 +0800
Subject: [PATCH v3 4/5] Use replay position as floor for WAIT FOR LSN
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index c30fba6f05a..d4d66b0e1f2 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 5be84bd4566..a35692a50ce 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index bf61b8c47cf..8e8776f3ea4 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -652,4 +652,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CAPpHfduhQsm44j_ziZ6ykFTDZ2SFvZp04iCrc5_dD9PGZUrhrw@mail.gmail.com/5-v3-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From 0e5d928f9c949e07bd9e1ab177154e2c0a2fc3ce Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Thu, 9 Apr 2026 18:09:04 +0300
Subject: [PATCH v3 3/5] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f85b5286086..b5801ab663e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 09fde92bfd7..d6fdea5578f 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,12 +978,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1025,13 +1023,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (6.0K, ../../CAPpHfduhQsm44j_ziZ6ykFTDZ2SFvZp04iCrc5_dD9PGZUrhrw@mail.gmail.com/6-v3-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From 7349983358c7223dfbeb700cb44c6464ca89007f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Thu, 9 Apr 2026 18:07:35 +0300
Subject: [PATCH v3 5/5] Wake standby_write/standby_flush waiters from the WAL
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 8e8776f3ea4..8f623c475db 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -668,6 +668,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -722,6 +733,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-09 16:18                                                                                                                                                                         ` Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Andres Freund @ 2026-04-09 16:18 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On 2026-04-09 18:21:24 +0300, Alexander Korotkov wrote:
> I've assembled all the pending patches together.
> 0001 adds memory barrier to GetWalRcvWriteRecPtr() as suggested by
> Andres off-list.

I'd make it a pg_atomic_read_membarrier_u64().


> 0002 is basically [1] by Xuneng, but revised given we have a memory
> barrier in 0001, and my proposal to do ResetLatch() unconditionally
> similar to our other Latch-based loops.
> 0003 and 0004 are [2] by Xuneng.
> 0005 is [3] by Xuneng.
> 
> I'm going to add them to Commitfest to run CI over them, and have a
> closer look over them tomorrow.

Briefly skimming the patches, none makes the writes to writtenUpto use
something bearing barrier semantics. I'd just make both of them a
pg_atomic_write_membarrier_u64().


I think this also needs a few more tests, e.g. for the scenario that
29e7dbf5e4d fixed.  I think it'd also be good to do some testing for
off-by-one dangers. E.g. making sure that we don't stop waiting too early /
too late.  Another one that I think might deserve more testing is waits on the
standby while crossing timeline boundaries.



> From 0e5b4d1b9311a628a70218d89abf12308c9d782f Mon Sep 17 00:00:00 2001
> From: Alexander Korotkov <[email protected]>
> Date: Thu, 9 Apr 2026 16:49:04 +0300
> Subject: [PATCH v3 1/5] Add a memory barrier to GetWalRcvWriteRecPtr()
> 
> Add pg_memory_barrier() before reading writtenUpto so that callers see
> up-to-date shared memory state.  This matches the barrier semantics that
> GetWalRcvFlushRecPtr() and other LSN-position functions get implicitly from
> their spinlock acquire/release, and in turn protects from bugs caused by
> expectations of similar barrier guarantees from different LSN-position functions.
> 
> Reported-by: Andres Freund <[email protected]>
> Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
> ---
>  src/backend/replication/walreceiverfuncs.c | 12 ++++++++++--
>  1 file changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
> index bd5d47be964..0408ddff43e 100644
> --- a/src/backend/replication/walreceiverfuncs.c
> +++ b/src/backend/replication/walreceiverfuncs.c
> @@ -363,14 +363,22 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
>  
>  /*
>   * Returns the last+1 byte position that walreceiver has written.
> - * This returns a recently written value without taking a lock.
> + *
> + * Use a memory barrier to ensure that callers see up-to-date shared memory
> + * state, matching the barrier semantics provided by the spinlock in
> + * GetWalRcvFlushRecPtr() and other LSN-position functions.
>   */
>  XLogRecPtr
>  GetWalRcvWriteRecPtr(void)
>  {
>  	WalRcvData *walrcv = WalRcv;
> +	XLogRecPtr	recptr;
> +
> +	pg_memory_barrier();
>  
> -	return pg_atomic_read_u64(&walrcv->writtenUpto);
> +	recptr = pg_atomic_read_u64(&walrcv->writtenUpto);
> +
> +	return recptr;
>  }
>  
>  /*
> -- 
> 2.39.5 (Apple Git-154)
> 

> Subject: [PATCH v3 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism

> +	/*
> +	 * Ensure the waker's prior position store (writtenUpto, flushedUpto,
> +	 * lastReplayedEndRecPtr, etc.) is globally visible before we read
> +	 * minWaitedLSN.  Without this barrier, the CPU could load minWaitedLSN
> +	 * before draining the position store, leaving the position invisible to a
> +	 * concurrently-registering waiter.
> +	 *
> +	 * This is the waker side of a Dekker-style handshake; pairs with
> +	 * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side.
> +	 */
> +	pg_memory_barrier();
> +
>  	/*
>  	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
>  	 * "wake all waiters" (e.g., during promotion when recovery ends).

I'd also make this a pg_atomic_read_membarrier_u64() and the write a
pg_atomic_write_membarrier_u64().  It's a lot easier to reason about this
stuff if you make sure that the individual reads / write pair and have
ordering implied.



Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-04-10 03:59                                                                                                                                                                           ` Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-10 03:59 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Fri, Apr 10, 2026 at 12:18 AM Andres Freund <[email protected]> wrote:
>
> On 2026-04-09 18:21:24 +0300, Alexander Korotkov wrote:
> > I've assembled all the pending patches together.
> > 0001 adds memory barrier to GetWalRcvWriteRecPtr() as suggested by
> > Andres off-list.
>
> I'd make it a pg_atomic_read_membarrier_u64().
>
>
> > 0002 is basically [1] by Xuneng, but revised given we have a memory
> > barrier in 0001, and my proposal to do ResetLatch() unconditionally
> > similar to our other Latch-based loops.
> > 0003 and 0004 are [2] by Xuneng.
> > 0005 is [3] by Xuneng.
> >
> > I'm going to add them to Commitfest to run CI over them, and have a
> > closer look over them tomorrow.
>
> Briefly skimming the patches, none makes the writes to writtenUpto use
> something bearing barrier semantics. I'd just make both of them a
> pg_atomic_write_membarrier_u64().
>

Makes sense to me. Done.

> I think this also needs a few more tests, e.g. for the scenario that
> 29e7dbf5e4d fixed.  I think it'd also be good to do some testing for
> off-by-one dangers. E.g. making sure that we don't stop waiting too early /
> too late.  Another one that I think might deserve more testing is waits on the
> standby while crossing timeline boundaries.
>

I'll prepare a new patch for more test harnessing.

>
> > From 0e5b4d1b9311a628a70218d89abf12308c9d782f Mon Sep 17 00:00:00 2001
> > From: Alexander Korotkov <[email protected]>
> > Date: Thu, 9 Apr 2026 16:49:04 +0300
> > Subject: [PATCH v3 1/5] Add a memory barrier to GetWalRcvWriteRecPtr()
> >
> > Add pg_memory_barrier() before reading writtenUpto so that callers see
> > up-to-date shared memory state.  This matches the barrier semantics that
> > GetWalRcvFlushRecPtr() and other LSN-position functions get implicitly from
> > their spinlock acquire/release, and in turn protects from bugs caused by
> > expectations of similar barrier guarantees from different LSN-position functions.
> >
> > Reported-by: Andres Freund <[email protected]>
> > Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
> > ---
> >  src/backend/replication/walreceiverfuncs.c | 12 ++++++++++--
> >  1 file changed, 10 insertions(+), 2 deletions(-)
> >
> > diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
> > index bd5d47be964..0408ddff43e 100644
> > --- a/src/backend/replication/walreceiverfuncs.c
> > +++ b/src/backend/replication/walreceiverfuncs.c
> > @@ -363,14 +363,22 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
> >
> >  /*
> >   * Returns the last+1 byte position that walreceiver has written.
> > - * This returns a recently written value without taking a lock.
> > + *
> > + * Use a memory barrier to ensure that callers see up-to-date shared memory
> > + * state, matching the barrier semantics provided by the spinlock in
> > + * GetWalRcvFlushRecPtr() and other LSN-position functions.
> >   */
> >  XLogRecPtr
> >  GetWalRcvWriteRecPtr(void)
> >  {
> >       WalRcvData *walrcv = WalRcv;
> > +     XLogRecPtr      recptr;
> > +
> > +     pg_memory_barrier();
> >
> > -     return pg_atomic_read_u64(&walrcv->writtenUpto);
> > +     recptr = pg_atomic_read_u64(&walrcv->writtenUpto);
> > +
> > +     return recptr;
> >  }
> >
> >  /*
> > --
> > 2.39.5 (Apple Git-154)
> >
>
> > Subject: [PATCH v3 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism
>
> > +     /*
> > +      * Ensure the waker's prior position store (writtenUpto, flushedUpto,
> > +      * lastReplayedEndRecPtr, etc.) is globally visible before we read
> > +      * minWaitedLSN.  Without this barrier, the CPU could load minWaitedLSN
> > +      * before draining the position store, leaving the position invisible to a
> > +      * concurrently-registering waiter.
> > +      *
> > +      * This is the waker side of a Dekker-style handshake; pairs with
> > +      * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side.
> > +      */
> > +     pg_memory_barrier();
> > +
> >       /*
> >        * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> >        * "wake all waiters" (e.g., during promotion when recovery ends).
>
> I'd also make this a pg_atomic_read_membarrier_u64() and the write a
> pg_atomic_write_membarrier_u64().  It's a lot easier to reason about this
> stuff if you make sure that the individual reads / write pair and have
> ordering implied.
>

It does look more selft-contained to me.

Here is the updated patch set based on Alexander’s earlier version.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v4-0001-Use-barrier-semantics-when-reading-writing-writte.patch (3.0K, ../../CABPTF7W7=uK0ypteDLRTiR4PnRP-BEUEJJtit+Q17aiPPhpLrw@mail.gmail.com/2-v4-0001-Use-barrier-semantics-when-reading-writing-writte.patch)
  download | inline diff:
From 3bd688ce54aed58840e1602068f88c0b2b58db69 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:45:52 +0800
Subject: [PATCH v4 1/5] Use barrier semantics when reading/writing writtenUpto

The walreceiver publishes its write position lock-free via writtenUpto.
On weakly-ordered architectures (ARM, PowerPC), both sides of this
handshake need explicit barriers so that the lock-less reader sees a
consistent state.

Use pg_atomic_write_membarrier_u64() at both write sites and
pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr().  This matches
the barrier semantics that GetWalRcvFlushRecPtr() and other LSN-position
functions get implicitly from their spinlock acquire/release, and
protects from bugs caused by expectations of similar barrier guarantees
from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiver.c      |  2 +-
 src/backend/replication/walreceiverfuncs.c | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 09fde92bfd7..b4196e8a933 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -975,7 +975,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	}
 
 	/* Update shared-memory status */
-	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
+	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..9447913c0e7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -321,7 +321,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 		walrcv->flushedUpto = recptr;
 		walrcv->receivedTLI = tli;
 		walrcv->latestChunkStart = recptr;
-		pg_atomic_write_u64(&walrcv->writtenUpto, recptr);
+		/* Pairs with pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr(). */
+		pg_atomic_write_membarrier_u64(&walrcv->writtenUpto, recptr);
 	}
 	walrcv->receiveStart = recptr;
 	walrcv->receiveStartTLI = tli;
@@ -363,14 +364,17 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use pg_atomic_read_membarrier_u64() to ensure that callers see up-to-date
+ * shared memory state, matching the barrier semantics provided by the
+ * spinlock in GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	return pg_atomic_read_membarrier_u64(&walrcv->writtenUpto);
 }
 
 /*
-- 
2.51.0



  [application/octet-stream] v4-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN.patch (8.7K, ../../CABPTF7W7=uK0ypteDLRTiR4PnRP-BEUEJJtit+Q17aiPPhpLrw@mail.gmail.com/3-v4-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN.patch)
  download | inline diff:
From 7b6f575d0e9935a0197d20ed64fda3d3158cb364 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:07:54 +0800
Subject: [PATCH v4 4/5] Use replay position as floor for WAIT FOR LSN 
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index c30fba6f05a..d4d66b0e1f2 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6a27183c207..18f78338330 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index bf61b8c47cf..8e8776f3ea4 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -652,4 +652,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.51.0



  [application/octet-stream] v4-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (4.2K, ../../CABPTF7W7=uK0ypteDLRTiR4PnRP-BEUEJJtit+Q17aiPPhpLrw@mail.gmail.com/4-v4-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From da465aac7e94baca11510b9d734b5ad5367d792e Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:59:13 +0800
Subject: [PATCH v4 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN
position then reads minWaitedLSN; the waiter stores its target into
minWaitedLSN then reads the position.  Without a barrier between each
side's store and load, a CPU may satisfy the load before the store
becomes globally visible, causing either side to miss a concurrent
update.  The result is a missed wakeup: the waiter sleeps indefinitely
until the next unrelated event.

Fix by embedding the required barriers into the atomic operations on
minWaitedLSN:

- In updateMinWaitedLSN(), use pg_atomic_write_membarrier_u64() so the
  waiter's preceding heap update is visible before the new minWaitedLSN
  value is published.

- In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
  fast-path check so the waker's preceding position store is globally
  visible before minWaitedLSN is read.

The waiter side is also covered by the barrier semantics already present
in GetCurrentLSNForWaitType(): GetWalRcvWriteRecPtr() uses an explicit
read barrier (from patch 0001), while the remaining getters acquire a
spinlock, which implies the same ordering.

Also call ResetLatch() unconditionally after WaitLatch().  The previous
conditional 'if (rc & WL_LATCH_SET)' could skip ResetLatch() on a
timeout return, omitting its memory barrier.  Without that barrier, the
GetCurrentLSNForWaitType() call at the top of the next loop iteration
could read a stale position, potentially causing the waiter to exit with
WAIT_LSN_RESULT_TIMEOUT even though the target LSN has been reached.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6a27183c207 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provide memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -184,7 +190,8 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
-	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+	/* Pairs with pg_atomic_read_membarrier_u64() in WaitLSNWakeup(). */
+	pg_atomic_write_membarrier_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
 }
 
 /*
@@ -325,10 +332,11 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
-	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 * "wake all waiters" (e.g., during promotion when recovery ends). Pairs
+	 * with pg_atomic_write_membarrier_u64() in updateMinWaitedLSN().
 	 */
 	if (XLogRecPtrIsValid(currentLSN) &&
-		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		pg_atomic_read_membarrier_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
@@ -450,8 +458,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.51.0



  [application/octet-stream] v4-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CABPTF7W7=uK0ypteDLRTiR4PnRP-BEUEJJtit+Q17aiPPhpLrw@mail.gmail.com/5-v4-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From c923701e9e244f854e75dd04d1aaeacdae80a1a7 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:05:46 +0800
Subject: [PATCH v4 3/5] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f85b5286086..b5801ab663e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index b4196e8a933..dab9c70584d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,12 +978,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1025,13 +1023,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.51.0



  [application/octet-stream] v4-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (5.9K, ../../CABPTF7W7=uK0ypteDLRTiR4PnRP-BEUEJJtit+Q17aiPPhpLrw@mail.gmail.com/6-v4-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From e5d8721c0b1a25b2e1c6a87d76f83fec13868786 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v4 5/5] Wake standby_write/standby_flush waiters from the WAL 
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 8e8776f3ea4..8f623c475db 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -668,6 +668,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -722,6 +733,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-10 06:13                                                                                                                                                                             ` Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-10 06:13 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Fri, Apr 10, 2026 at 11:59 AM Xuneng Zhou <[email protected]> wrote:
>
> On Fri, Apr 10, 2026 at 12:18 AM Andres Freund <[email protected]> wrote:
> >
> > On 2026-04-09 18:21:24 +0300, Alexander Korotkov wrote:
> > > I've assembled all the pending patches together.
> > > 0001 adds memory barrier to GetWalRcvWriteRecPtr() as suggested by
> > > Andres off-list.
> >
> > I'd make it a pg_atomic_read_membarrier_u64().
> >
> >
> > > 0002 is basically [1] by Xuneng, but revised given we have a memory
> > > barrier in 0001, and my proposal to do ResetLatch() unconditionally
> > > similar to our other Latch-based loops.
> > > 0003 and 0004 are [2] by Xuneng.
> > > 0005 is [3] by Xuneng.
> > >
> > > I'm going to add them to Commitfest to run CI over them, and have a
> > > closer look over them tomorrow.
> >
> > Briefly skimming the patches, none makes the writes to writtenUpto use
> > something bearing barrier semantics. I'd just make both of them a
> > pg_atomic_write_membarrier_u64().
> >
>
> Makes sense to me. Done.
>
> > I think this also needs a few more tests, e.g. for the scenario that
> > 29e7dbf5e4d fixed.  I think it'd also be good to do some testing for
> > off-by-one dangers. E.g. making sure that we don't stop waiting too early /
> > too late.  Another one that I think might deserve more testing is waits on the
> > standby while crossing timeline boundaries.
> >
>
> I'll prepare a new patch for more test harnessing.
>
> >
> > > From 0e5b4d1b9311a628a70218d89abf12308c9d782f Mon Sep 17 00:00:00 2001
> > > From: Alexander Korotkov <[email protected]>
> > > Date: Thu, 9 Apr 2026 16:49:04 +0300
> > > Subject: [PATCH v3 1/5] Add a memory barrier to GetWalRcvWriteRecPtr()
> > >
> > > Add pg_memory_barrier() before reading writtenUpto so that callers see
> > > up-to-date shared memory state.  This matches the barrier semantics that
> > > GetWalRcvFlushRecPtr() and other LSN-position functions get implicitly from
> > > their spinlock acquire/release, and in turn protects from bugs caused by
> > > expectations of similar barrier guarantees from different LSN-position functions.
> > >
> > > Reported-by: Andres Freund <[email protected]>
> > > Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
> > > ---
> > >  src/backend/replication/walreceiverfuncs.c | 12 ++++++++++--
> > >  1 file changed, 10 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
> > > index bd5d47be964..0408ddff43e 100644
> > > --- a/src/backend/replication/walreceiverfuncs.c
> > > +++ b/src/backend/replication/walreceiverfuncs.c
> > > @@ -363,14 +363,22 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
> > >
> > >  /*
> > >   * Returns the last+1 byte position that walreceiver has written.
> > > - * This returns a recently written value without taking a lock.
> > > + *
> > > + * Use a memory barrier to ensure that callers see up-to-date shared memory
> > > + * state, matching the barrier semantics provided by the spinlock in
> > > + * GetWalRcvFlushRecPtr() and other LSN-position functions.
> > >   */
> > >  XLogRecPtr
> > >  GetWalRcvWriteRecPtr(void)
> > >  {
> > >       WalRcvData *walrcv = WalRcv;
> > > +     XLogRecPtr      recptr;
> > > +
> > > +     pg_memory_barrier();
> > >
> > > -     return pg_atomic_read_u64(&walrcv->writtenUpto);
> > > +     recptr = pg_atomic_read_u64(&walrcv->writtenUpto);
> > > +
> > > +     return recptr;
> > >  }
> > >
> > >  /*
> > > --
> > > 2.39.5 (Apple Git-154)
> > >
> >
> > > Subject: [PATCH v3 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism
> >
> > > +     /*
> > > +      * Ensure the waker's prior position store (writtenUpto, flushedUpto,
> > > +      * lastReplayedEndRecPtr, etc.) is globally visible before we read
> > > +      * minWaitedLSN.  Without this barrier, the CPU could load minWaitedLSN
> > > +      * before draining the position store, leaving the position invisible to a
> > > +      * concurrently-registering waiter.
> > > +      *
> > > +      * This is the waker side of a Dekker-style handshake; pairs with
> > > +      * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side.
> > > +      */
> > > +     pg_memory_barrier();
> > > +
> > >       /*
> > >        * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> > >        * "wake all waiters" (e.g., during promotion when recovery ends).
> >
> > I'd also make this a pg_atomic_read_membarrier_u64() and the write a
> > pg_atomic_write_membarrier_u64().  It's a lot easier to reason about this
> > stuff if you make sure that the individual reads / write pair and have
> > ordering implied.
> >
>
> It does look more selft-contained to me.
>
> Here is the updated patch set based on Alexander’s earlier version.

The last para of commit message in patch 2 is inaccurate after the
getter-side barrier changes, "could read a stale position and wrongly
timeout" is no longer the primary rationale.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v4-0001-Use-barrier-semantics-when-reading-writing-writte.patch (3.0K, ../../CABPTF7Ukk8iJF7TpnK2mFOaboNJgWL1csfXu4e3J4GT0o7x0GQ@mail.gmail.com/2-v4-0001-Use-barrier-semantics-when-reading-writing-writte.patch)
  download | inline diff:
From 3bd688ce54aed58840e1602068f88c0b2b58db69 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:45:52 +0800
Subject: [PATCH v4 1/5] Use barrier semantics when reading/writing writtenUpto

The walreceiver publishes its write position lock-free via writtenUpto.
On weakly-ordered architectures (ARM, PowerPC), both sides of this
handshake need explicit barriers so that the lock-less reader sees a
consistent state.

Use pg_atomic_write_membarrier_u64() at both write sites and
pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr().  This matches
the barrier semantics that GetWalRcvFlushRecPtr() and other LSN-position
functions get implicitly from their spinlock acquire/release, and
protects from bugs caused by expectations of similar barrier guarantees
from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiver.c      |  2 +-
 src/backend/replication/walreceiverfuncs.c | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 09fde92bfd7..b4196e8a933 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -975,7 +975,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	}
 
 	/* Update shared-memory status */
-	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
+	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..9447913c0e7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -321,7 +321,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 		walrcv->flushedUpto = recptr;
 		walrcv->receivedTLI = tli;
 		walrcv->latestChunkStart = recptr;
-		pg_atomic_write_u64(&walrcv->writtenUpto, recptr);
+		/* Pairs with pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr(). */
+		pg_atomic_write_membarrier_u64(&walrcv->writtenUpto, recptr);
 	}
 	walrcv->receiveStart = recptr;
 	walrcv->receiveStartTLI = tli;
@@ -363,14 +364,17 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use pg_atomic_read_membarrier_u64() to ensure that callers see up-to-date
+ * shared memory state, matching the barrier semantics provided by the
+ * spinlock in GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	return pg_atomic_read_membarrier_u64(&walrcv->writtenUpto);
 }
 
 /*
-- 
2.51.0



  [application/octet-stream] v4-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (5.9K, ../../CABPTF7Ukk8iJF7TpnK2mFOaboNJgWL1csfXu4e3J4GT0o7x0GQ@mail.gmail.com/3-v4-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From e5d8721c0b1a25b2e1c6a87d76f83fec13868786 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v4 5/5] Wake standby_write/standby_flush waiters from the WAL 
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 8e8776f3ea4..8f623c475db 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -668,6 +668,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -722,6 +733,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.51.0



  [application/octet-stream] v4-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN.patch (8.7K, ../../CABPTF7Ukk8iJF7TpnK2mFOaboNJgWL1csfXu4e3J4GT0o7x0GQ@mail.gmail.com/4-v4-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN.patch)
  download | inline diff:
From 7b6f575d0e9935a0197d20ed64fda3d3158cb364 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:07:54 +0800
Subject: [PATCH v4 4/5] Use replay position as floor for WAIT FOR LSN 
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index c30fba6f05a..d4d66b0e1f2 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6a27183c207..18f78338330 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index bf61b8c47cf..8e8776f3ea4 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -652,4 +652,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.51.0



  [application/octet-stream] v4-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CABPTF7Ukk8iJF7TpnK2mFOaboNJgWL1csfXu4e3J4GT0o7x0GQ@mail.gmail.com/5-v4-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From c923701e9e244f854e75dd04d1aaeacdae80a1a7 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:05:46 +0800
Subject: [PATCH v4 3/5] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f85b5286086..b5801ab663e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index b4196e8a933..dab9c70584d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,12 +978,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1025,13 +1023,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.51.0



  [application/octet-stream] v4-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (4.3K, ../../CABPTF7Ukk8iJF7TpnK2mFOaboNJgWL1csfXu4e3J4GT0o7x0GQ@mail.gmail.com/6-v4-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From da465aac7e94baca11510b9d734b5ad5367d792e Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:59:13 +0800
Subject: [PATCH v4 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN
position then reads minWaitedLSN; the waiter stores its target into
minWaitedLSN then reads the position.  Without a barrier between each
side's store and load, a CPU may satisfy the load before the store
becomes globally visible, causing either side to miss a concurrent
update.  The result is a missed wakeup: the waiter sleeps indefinitely
until the next unrelated event.

Fix by embedding the required barriers into the atomic operations on
minWaitedLSN:

- In updateMinWaitedLSN(), use pg_atomic_write_membarrier_u64() so the
  waiter's preceding heap update is visible before the new minWaitedLSN
  value is published.

- In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
  fast-path check so the waker's preceding position store is globally
  visible before minWaitedLSN is read.

The waiter side is also covered by the barrier semantics already present
in GetCurrentLSNForWaitType(): GetWalRcvWriteRecPtr() uses an explicit
read barrier (from patch 0001), while the remaining getters acquire a
spinlock, which implies the same ordering.

Also call ResetLatch() unconditionally after WaitLatch(), following the
standard latch loop pattern.  WaitLatch() does not guarantee that all
simultaneously true wake conditions are reported in one return, so a
timeout can race with SetLatch().  If we skip ResetLatch() on a timeout
return, the code performs further asynchronous-state checks before
consuming the latch, violating the latch API's required wait/reset
pattern.  That can leave the latch set across loop exit and cause a
later unrelated WaitLatch() in the same backend to return immediately.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6a27183c207 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provide memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -184,7 +190,8 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
-	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+	/* Pairs with pg_atomic_read_membarrier_u64() in WaitLSNWakeup(). */
+	pg_atomic_write_membarrier_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
 }
 
 /*
@@ -325,10 +332,11 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
-	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 * "wake all waiters" (e.g., during promotion when recovery ends). Pairs
+	 * with pg_atomic_write_membarrier_u64() in updateMinWaitedLSN().
 	 */
 	if (XLogRecPtrIsValid(currentLSN) &&
-		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		pg_atomic_read_membarrier_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
@@ -450,8 +458,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-20 18:45                                                                                                                                                                               ` Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-20 18:45 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Fri, Apr 10, 2026 at 9:13 AM Xuneng Zhou <[email protected]> wrote:
>
> On Fri, Apr 10, 2026 at 11:59 AM Xuneng Zhou <[email protected]> wrote:
> >
> > On Fri, Apr 10, 2026 at 12:18 AM Andres Freund <[email protected]> wrote:
> > >
> > > On 2026-04-09 18:21:24 +0300, Alexander Korotkov wrote:
> > > > I've assembled all the pending patches together.
> > > > 0001 adds memory barrier to GetWalRcvWriteRecPtr() as suggested by
> > > > Andres off-list.
> > >
> > > I'd make it a pg_atomic_read_membarrier_u64().
> > >
> > >
> > > > 0002 is basically [1] by Xuneng, but revised given we have a memory
> > > > barrier in 0001, and my proposal to do ResetLatch() unconditionally
> > > > similar to our other Latch-based loops.
> > > > 0003 and 0004 are [2] by Xuneng.
> > > > 0005 is [3] by Xuneng.
> > > >
> > > > I'm going to add them to Commitfest to run CI over them, and have a
> > > > closer look over them tomorrow.
> > >
> > > Briefly skimming the patches, none makes the writes to writtenUpto use
> > > something bearing barrier semantics. I'd just make both of them a
> > > pg_atomic_write_membarrier_u64().
> > >
> >
> > Makes sense to me. Done.
> >
> > > I think this also needs a few more tests, e.g. for the scenario that
> > > 29e7dbf5e4d fixed.  I think it'd also be good to do some testing for
> > > off-by-one dangers. E.g. making sure that we don't stop waiting too early /
> > > too late.  Another one that I think might deserve more testing is waits on the
> > > standby while crossing timeline boundaries.
> > >
> >
> > I'll prepare a new patch for more test harnessing.
> >
> > >
> > > > From 0e5b4d1b9311a628a70218d89abf12308c9d782f Mon Sep 17 00:00:00 2001
> > > > From: Alexander Korotkov <[email protected]>
> > > > Date: Thu, 9 Apr 2026 16:49:04 +0300
> > > > Subject: [PATCH v3 1/5] Add a memory barrier to GetWalRcvWriteRecPtr()
> > > >
> > > > Add pg_memory_barrier() before reading writtenUpto so that callers see
> > > > up-to-date shared memory state.  This matches the barrier semantics that
> > > > GetWalRcvFlushRecPtr() and other LSN-position functions get implicitly from
> > > > their spinlock acquire/release, and in turn protects from bugs caused by
> > > > expectations of similar barrier guarantees from different LSN-position functions.
> > > >
> > > > Reported-by: Andres Freund <[email protected]>
> > > > Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
> > > > ---
> > > >  src/backend/replication/walreceiverfuncs.c | 12 ++++++++++--
> > > >  1 file changed, 10 insertions(+), 2 deletions(-)
> > > >
> > > > diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
> > > > index bd5d47be964..0408ddff43e 100644
> > > > --- a/src/backend/replication/walreceiverfuncs.c
> > > > +++ b/src/backend/replication/walreceiverfuncs.c
> > > > @@ -363,14 +363,22 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
> > > >
> > > >  /*
> > > >   * Returns the last+1 byte position that walreceiver has written.
> > > > - * This returns a recently written value without taking a lock.
> > > > + *
> > > > + * Use a memory barrier to ensure that callers see up-to-date shared memory
> > > > + * state, matching the barrier semantics provided by the spinlock in
> > > > + * GetWalRcvFlushRecPtr() and other LSN-position functions.
> > > >   */
> > > >  XLogRecPtr
> > > >  GetWalRcvWriteRecPtr(void)
> > > >  {
> > > >       WalRcvData *walrcv = WalRcv;
> > > > +     XLogRecPtr      recptr;
> > > > +
> > > > +     pg_memory_barrier();
> > > >
> > > > -     return pg_atomic_read_u64(&walrcv->writtenUpto);
> > > > +     recptr = pg_atomic_read_u64(&walrcv->writtenUpto);
> > > > +
> > > > +     return recptr;
> > > >  }
> > > >
> > > >  /*
> > > > --
> > > > 2.39.5 (Apple Git-154)
> > > >
> > >
> > > > Subject: [PATCH v3 2/5] Fix memory ordering in WAIT FOR LSN wakeup mechanism
> > >
> > > > +     /*
> > > > +      * Ensure the waker's prior position store (writtenUpto, flushedUpto,
> > > > +      * lastReplayedEndRecPtr, etc.) is globally visible before we read
> > > > +      * minWaitedLSN.  Without this barrier, the CPU could load minWaitedLSN
> > > > +      * before draining the position store, leaving the position invisible to a
> > > > +      * concurrently-registering waiter.
> > > > +      *
> > > > +      * This is the waker side of a Dekker-style handshake; pairs with
> > > > +      * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side.
> > > > +      */
> > > > +     pg_memory_barrier();
> > > > +
> > > >       /*
> > > >        * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> > > >        * "wake all waiters" (e.g., during promotion when recovery ends).
> > >
> > > I'd also make this a pg_atomic_read_membarrier_u64() and the write a
> > > pg_atomic_write_membarrier_u64().  It's a lot easier to reason about this
> > > stuff if you make sure that the individual reads / write pair and have
> > > ordering implied.
> > >
> >
> > It does look more selft-contained to me.
> >
> > Here is the updated patch set based on Alexander’s earlier version.
>
> The last para of commit message in patch 2 is inaccurate after the
> getter-side barrier changes, "could read a stale position and wrongly
> timeout" is no longer the primary rationale.

The updated patchset is attached.  It includes improved coverage as
suggested by Andres upthread.  And documentation that WAIT FOR LSN is
timeline-blind (per off-list discussion with Xuneng).

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v5-0001-Use-barrier-semantics-when-reading-writing-writte.patch (3.1K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/2-v5-0001-Use-barrier-semantics-when-reading-writing-writte.patch)
  download | inline diff:
From 80b7d2d257dcf5a23a1851fd5bc71fe78bb604fc Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:45:52 +0800
Subject: [PATCH v5 1/7] Use barrier semantics when reading/writing writtenUpto

The walreceiver publishes its write position lock-free via writtenUpto.
On weakly-ordered architectures (ARM, PowerPC), both sides of this
handshake need explicit barriers so that the lock-less reader sees a
consistent state.

Use pg_atomic_write_membarrier_u64() at both write sites and
pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr().  This matches
the barrier semantics that GetWalRcvFlushRecPtr() and other LSN-position
functions get implicitly from their spinlock acquire/release, and
protects from bugs caused by expectations of similar barrier guarantees
from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiver.c      |  2 +-
 src/backend/replication/walreceiverfuncs.c | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 8185412a810..c0ac75b54d7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -975,7 +975,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	}
 
 	/* Update shared-memory status */
-	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
+	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..9447913c0e7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -321,7 +321,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 		walrcv->flushedUpto = recptr;
 		walrcv->receivedTLI = tli;
 		walrcv->latestChunkStart = recptr;
-		pg_atomic_write_u64(&walrcv->writtenUpto, recptr);
+		/* Pairs with pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr(). */
+		pg_atomic_write_membarrier_u64(&walrcv->writtenUpto, recptr);
 	}
 	walrcv->receiveStart = recptr;
 	walrcv->receiveStartTLI = tli;
@@ -363,14 +364,17 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use pg_atomic_read_membarrier_u64() to ensure that callers see up-to-date
+ * shared memory state, matching the barrier semantics provided by the
+ * spinlock in GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	return pg_atomic_read_membarrier_u64(&walrcv->writtenUpto);
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (4.3K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/3-v5-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From 8d82dd6c64b0cd6f09904a5ceeca361dd714c4f5 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:59:13 +0800
Subject: [PATCH v5 2/7] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN
position then reads minWaitedLSN; the waiter stores its target into
minWaitedLSN then reads the position.  Without a barrier between each
side's store and load, a CPU may satisfy the load before the store
becomes globally visible, causing either side to miss a concurrent
update.  The result is a missed wakeup: the waiter sleeps indefinitely
until the next unrelated event.

Fix by embedding the required barriers into the atomic operations on
minWaitedLSN:

- In updateMinWaitedLSN(), use pg_atomic_write_membarrier_u64() so the
  waiter's preceding heap update is visible before the new minWaitedLSN
  value is published.

- In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
  fast-path check so the waker's preceding position store is globally
  visible before minWaitedLSN is read.

The waiter side is also covered by the barrier semantics already present
in GetCurrentLSNForWaitType(): GetWalRcvWriteRecPtr() uses an explicit
read barrier (from patch 0001), while the remaining getters acquire a
spinlock, which implies the same ordering.

Also call ResetLatch() unconditionally after WaitLatch(), following the
standard latch loop pattern.  WaitLatch() does not guarantee that all
simultaneously true wake conditions are reported in one return, so a
timeout can race with SetLatch().  If we skip ResetLatch() on a timeout
return, the code performs further asynchronous-state checks before
consuming the latch, violating the latch API's required wait/reset
pattern.  That can leave the latch set across loop exit and cause a
later unrelated WaitLatch() in the same backend to return immediately.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6a27183c207 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provide memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -184,7 +190,8 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
-	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+	/* Pairs with pg_atomic_read_membarrier_u64() in WaitLSNWakeup(). */
+	pg_atomic_write_membarrier_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
 }
 
 /*
@@ -325,10 +332,11 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
-	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 * "wake all waiters" (e.g., during promotion when recovery ends). Pairs
+	 * with pg_atomic_write_membarrier_u64() in updateMinWaitedLSN().
 	 */
 	if (XLogRecPtrIsValid(currentLSN) &&
-		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		pg_atomic_read_membarrier_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
@@ -450,8 +458,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/4-v5-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From e0148f85e9fa557069a8c32b0ab3ba0efeb2c600 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:05:46 +0800
Subject: [PATCH v5 3/7] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f85b5286086..b5801ab663e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c0ac75b54d7..8ed796f3033 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,12 +978,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1025,13 +1023,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch (8.7K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/5-v5-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch)
  download | inline diff:
From 79e18b79cc0a037802903d852199d2fb7058df63 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:07:54 +0800
Subject: [PATCH v5 4/7] Use replay position as floor for WAIT FOR LSN
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 9ba785ea321..7b403c98dd0 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6a27183c207..18f78338330 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 0e74175f9eb..26790fda5be 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -674,4 +674,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (5.9K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/6-v5-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From 9cd16c4b02c0f6f91c5ab9d1e76ad1b0f288d725 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v5 5/7] Wake standby_write/standby_flush waiters from the WAL
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 26790fda5be..d2610cf0856 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -690,6 +690,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -744,6 +755,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0006-Improve-WAIT-FOR-LSN-test-coverage.patch (10.3K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/7-v5-0006-Improve-WAIT-FOR-LSN-test-coverage.patch)
  download | inline diff:
From 934cefe8e61d5d427105ab2f0f65579ada219eaa Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 20 Apr 2026 21:27:29 +0300
Subject: [PATCH v5 6/7] Improve WAIT FOR LSN test coverage

1. Check the values of writtenUpto/flushedUpto initialized by
   RequestXLogStreaming().  Check WAIT FOR LSN succeeds in both
   standby_write / standby_flush modes thanks to replay-position floor in
   GetCurrentLSNForWaitType()

2. Off-by-one boundary checks for the wait predicate target <= currentLSN.
   With replay paused and the walreceiver stopped, verify that targets at
   current and current - 1 succeed immediately, that current + 1 times out,
   and that a waiter at current + 1 wakes once replay actually advances past
   it.

3. Timeline switch on a cascade standby.  A waiter on a cascade standby must
   survive its upstream's promotion: the cascade walreceiver reconnects on
   the new timeline and the wait completes when WAL on the new timeline
   reaches the target.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Alexander Korotkov <[email protected]>
---
 src/test/recovery/t/049_wait_for_lsn.pl | 226 ++++++++++++++++++++++++
 1 file changed, 226 insertions(+)

diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index d2610cf0856..c386425a7fd 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -824,4 +824,230 @@ ok(1,
 $arc_standby->stop;
 $arc_primary->stop;
 
+# 10. Fresh-shmem walreceiver startup (29e7dbf5e4d).
+# RequestXLogStreaming() initializes writtenUpto/flushedUpto to the
+# segment-aligned receiveStart only when receiveStart was invalid.
+# Restart the standby with the primary stopped, so the walreceiver cannot
+# connect and advance these values past the initial one before we observe it.
+
+my $rcv_primary = PostgreSQL::Test::Cluster->new('rcv_primary');
+$rcv_primary->init(allows_streaming => 1);
+# No background WAL during our probes.
+$rcv_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"CREATE TABLE rcv_test AS SELECT generate_series(1,10) AS a");
+
+my $rcv_backup = 'rcv_backup';
+$rcv_primary->backup($rcv_backup);
+
+my $rcv_standby = PostgreSQL::Test::Cluster->new('rcv_standby');
+$rcv_standby->init_from_backup($rcv_primary, $rcv_backup, has_streaming => 1);
+$rcv_standby->start;
+
+# Switch WAL segments mid-stream so the replay ends mid-segment after the
+# upcoming standby restart.  That guarantees the initial value <
+# final replay LSN.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(11, 100))");
+$rcv_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(101, 110))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# Restart the standby with the primary down: WalRcvData is initialized, but
+# the walreceiver cannot connect and update writtenUpto/flushedUpto.  So,
+# the initial flushedUpto stays observable via pg_last_wal_receive_lsn()).
+$rcv_standby->stop;
+$rcv_primary->stop;
+$rcv_standby->start;
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_last_wal_receive_lsn() <> '0/0'::pg_lsn;")
+  or die "walreceiver initial value did not become visible";
+
+# Freeze the replay so the (received, replay] window stays observable.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+my $rcv_written =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $rcv_replay =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my $rcv_gap = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$rcv_replay'::pg_lsn, '$rcv_written'::pg_lsn) > 0"
+);
+ok($rcv_gap eq 't', "replay sits ahead of initial writtenUpto");
+
+# WAIT FOR an $rcv_replay LSN succeeds in standby_write / standby_flush
+# modes thanks to GetCurrentLSNForWaitType() taking replay LSN as the floor.
+foreach my $rcv_mode ('standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${rcv_replay}'
+			WITH (MODE '$rcv_mode', timeout '5s', no_throw);]);
+	ok($output eq "success",
+		"$rcv_mode succeeds for already-replayed LSN after standby restart");
+}
+
+# Restore primary and resume replay so section 11 can reuse the
+# clusters.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+$rcv_primary->start;
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# 11. Off-by-one boundary checks for the wait predicate target <=
+# currentLSN.  Stop the walreceiver before pausing replay (stopping
+# after pause can hang -- see section 7d) so both replay and
+# walreceiver positions are frozen.
+stop_walreceiver($rcv_standby);
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+my $boundary_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my $boundary_minus = $rcv_standby->safe_psql('postgres',
+	"SELECT ('$boundary_lsn'::pg_lsn - 1)::text");
+my $boundary_plus = $rcv_standby->safe_psql('postgres',
+	"SELECT ('$boundary_lsn'::pg_lsn + 1)::text");
+
+# 11a. target == current LSN succeeds immediately (predicate is <=).
+foreach my $b_mode ('standby_replay', 'standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${boundary_lsn}'
+			WITH (MODE '$b_mode', timeout '5s', no_throw);]);
+	ok($output eq "success", "$b_mode: target == current succeeds");
+}
+
+# 11b. target == current - 1 succeeds immediately.
+foreach my $b_mode ('standby_replay', 'standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${boundary_minus}'
+			WITH (MODE '$b_mode', timeout '5s', no_throw);]);
+	ok($output eq "success", "$b_mode: target == current - 1 succeeds");
+}
+
+# 11c. target == current + 1 must time out (no early success).
+foreach my $b_mode ('standby_replay', 'standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${boundary_plus}'
+			WITH (MODE '$b_mode', timeout '500ms', no_throw);]);
+	ok($output eq "timeout", "$b_mode: target == current + 1 times out");
+}
+
+# 11d. A sleeping waiter at current + 1 wakes once replay advances
+# past it.  Resume replay first (safe: walreceiver is stopped so no
+# new WAL arrives yet), start the waiter, then restart the
+# walreceiver to deliver the new WAL.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(200, 210))");
+
+my $boundary_log_offset = -s $rcv_standby->logfile;
+my $boundary_session = $rcv_standby->background_psql('postgres');
+$boundary_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${boundary_plus}'
+		WITH (MODE 'standby_replay', timeout '30s', no_throw);
+	DO \$\$ BEGIN RAISE LOG 'rcv_boundary_done'; END \$\$;
+]);
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Boundary waiter did not sleep";
+
+resume_walreceiver($rcv_standby);
+$rcv_standby->wait_for_log(qr/rcv_boundary_done/, $boundary_log_offset);
+$boundary_session->quit;
+
+ok(1, "standby_replay: waiter at current + 1 wakes when replay advances");
+
+$rcv_standby->stop;
+$rcv_primary->stop;
+
+# 12. Timeline switch on a cascade standby.  A WAIT FOR LSN waiter on
+# a cascade standby must survive its upstream's promotion: the
+# cascade walreceiver reconnects on the new timeline and replay
+# continues across the boundary.
+
+my $tl_primary = PostgreSQL::Test::Cluster->new('tl_primary');
+$tl_primary->init(allows_streaming => 1);
+$tl_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$tl_primary->start;
+$tl_primary->safe_psql('postgres',
+	"CREATE TABLE tl_test AS SELECT generate_series(1, 10) AS a");
+
+my $tl_backup = 'tl_backup';
+$tl_primary->backup($tl_backup);
+
+my $tl_standby1 = PostgreSQL::Test::Cluster->new('tl_standby1');
+$tl_standby1->init_from_backup($tl_primary, $tl_backup, has_streaming => 1);
+$tl_standby1->start;
+
+# standby2 cascades from standby1.
+my $tl_backup2 = 'tl_backup2';
+$tl_standby1->backup($tl_backup2);
+
+my $tl_standby2 = PostgreSQL::Test::Cluster->new('tl_standby2');
+$tl_standby2->init_from_backup($tl_standby1, $tl_backup2, has_streaming => 1);
+$tl_standby2->start;
+
+$tl_primary->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(11, 20))");
+$tl_primary->wait_for_catchup($tl_standby1);
+$tl_standby1->wait_for_catchup($tl_standby2);
+
+# Target LSN well past current insert LSN, so reaching it requires
+# WAL produced on the new timeline.  Pause replay on standby2 to
+# guarantee the waiter is asleep when the switch happens.
+my $tl_target = $tl_primary->safe_psql('postgres',
+	"SELECT (pg_current_wal_insert_lsn() + 65536)::text");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+my $tl_log_offset = -s $tl_standby2->logfile;
+my $tl_session = $tl_standby2->background_psql('postgres');
+$tl_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${tl_target}'
+		WITH (MODE 'standby_replay', timeout '60s', no_throw);
+	DO \$\$ BEGIN RAISE LOG 'tl_wait_done'; END \$\$;
+]);
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Cascade waiter did not sleep before promotion";
+
+# Promote standby1 to TLI 2; produce enough WAL on the new timeline
+# to push past tl_target and force a segment switch.
+$tl_standby1->promote;
+$tl_standby1->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(21, 1020))");
+$tl_standby1->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT received_tli > 1 FROM pg_stat_wal_receiver")
+  or die "tl_standby2 did not follow upstream timeline switch";
+
+$tl_standby2->wait_for_log(qr/tl_wait_done/, $tl_log_offset);
+$tl_session->quit;
+
+ok(1,
+	"WAIT FOR LSN survives upstream promotion and timeline switch on cascade standby"
+);
+
+$tl_standby2->stop;
+$tl_standby1->stop;
+$tl_primary->stop;
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch (1.9K, ../../CAPpHfdupZ3ooxmnJvhjx7rnZ7VMcz_uyxGC4drMSEM_P3PtGzw@mail.gmail.com/8-v5-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch)
  download | inline diff:
From eea6720c269ffd27a66892f175e5e6de8a92f729 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Mon, 20 Apr 2026 21:34:08 +0300
Subject: [PATCH v5 7/7] Document that WAIT FOR LSN is timeline-blind

WAIT FOR LSN compares only the numeric LSN and has no notion of which
timeline a WAL record belongs to.  There are many possible scenarios when
timeline-switching can break read-your-writes consistency.  The proper
analysis and timeline support is possible in the next major release.  Yet
just document the current behaviour.

Reported-by: Xuneng Zhou <[email protected]>
Author: Alexander Korotkov <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 7b403c98dd0..4283dc8a12f 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -256,6 +256,20 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    timeline.
   </para>
 
+  <para>
+   <command>WAIT FOR</command> compares only the numeric
+   <acronym>LSN</acronym>; it has no notion of which timeline a WAL
+   record belongs to.  This matters when a standby continues recovery
+   across an upstream timeline switch &mdash; for example, a cascading
+   standby whose upstream gets promoted.  In that case
+   <command>WAIT FOR</command> will return <literal>success</literal>
+   as soon as the local recovery position passes the numeric
+   <acronym>LSN</acronym>, regardless of which timeline that
+   <acronym>LSN</acronym> belongs to.  Applications that need to
+   confirm the target refers to the expected timeline must validate
+   the timeline themselves.
+  </para>
+
   <para>
    On a standby server, <command>WAIT FOR</command> sessions may be
    interrupted by recovery conflicts.  Some recovery conflicts are
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-21 04:03                                                                                                                                                                                 ` Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-21 04:03 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 21, 2026 at 2:46 AM Alexander Korotkov <[email protected]> wrote:

> The updated patchset is attached.  It includes improved coverage as
> suggested by Andres upthread.  And documentation that WAIT FOR LSN is
> timeline-blind (per off-list discussion with Xuneng).

I revised the test patch 6 to make the new cases check the intended
WAIT FOR behavior more directly, and to avoid cases where the test
could pass for the wrong reason.

The fresh walreceiver restart test now distinguishes what we can
observe from what is only covered indirectly.
'pg_last_wal_receive_lsn()' reports 'flushedUpto', not 'writtenUpto',
so the test now describes that state accurately and covers
'writtenUpto' through the 'standby_write' result. This seems
appropriate to me since the two positions are seeded in the places and
conditions. Test for flush lsn should also help verify write lsn.

The fencepost tests were split by the actual frontier being tested.
'standby_replay' uses 'pg_last_wal_replay_lsn()', while
'standby_flush' uses 'pg_last_wal_receive_lsn()'. This avoids treating
a replay-derived LSN as if it were also the exact write/flush
boundary. I left 'standby_write' out of the exact fencepost helper
because its frontier is not SQL-visible once walreceiver is stopped.
The async wakeup case now starts the waiter while replay is still
paused, so it must actually sleep before replay and walreceiver are
allowed to advance.

The cascading timeline-switch test now checks the 'WAIT FOR ...
NO_THROW' status from background psql stdout. The previous log-marker
pattern could pass after unexpected returned status, includingn
'timeout', because the following statement would still run. The
'received_tli > 1' check remains, but only as confirmation that the
downstream followed the new timeline; the 'success' status proves the
wait completed as intended.

Please check it.

--
Best,
Xuneng


Attachments:

  [application/octet-stream] v5-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/2-v5-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From e0148f85e9fa557069a8c32b0ab3ba0efeb2c600 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:05:46 +0800
Subject: [PATCH v5 3/7] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f85b5286086..b5801ab663e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c0ac75b54d7..8ed796f3033 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,12 +978,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1025,13 +1023,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (4.3K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/3-v5-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From 8d82dd6c64b0cd6f09904a5ceeca361dd714c4f5 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:59:13 +0800
Subject: [PATCH v5 2/7] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN
position then reads minWaitedLSN; the waiter stores its target into
minWaitedLSN then reads the position.  Without a barrier between each
side's store and load, a CPU may satisfy the load before the store
becomes globally visible, causing either side to miss a concurrent
update.  The result is a missed wakeup: the waiter sleeps indefinitely
until the next unrelated event.

Fix by embedding the required barriers into the atomic operations on
minWaitedLSN:

- In updateMinWaitedLSN(), use pg_atomic_write_membarrier_u64() so the
  waiter's preceding heap update is visible before the new minWaitedLSN
  value is published.

- In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
  fast-path check so the waker's preceding position store is globally
  visible before minWaitedLSN is read.

The waiter side is also covered by the barrier semantics already present
in GetCurrentLSNForWaitType(): GetWalRcvWriteRecPtr() uses an explicit
read barrier (from patch 0001), while the remaining getters acquire a
spinlock, which implies the same ordering.

Also call ResetLatch() unconditionally after WaitLatch(), following the
standard latch loop pattern.  WaitLatch() does not guarantee that all
simultaneously true wake conditions are reported in one return, so a
timeout can race with SetLatch().  If we skip ResetLatch() on a timeout
return, the code performs further asynchronous-state checks before
consuming the latch, violating the latch API's required wait/reset
pattern.  That can leave the latch set across loop exit and cause a
later unrelated WaitLatch() in the same backend to return immediately.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6a27183c207 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provide memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -184,7 +190,8 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
-	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+	/* Pairs with pg_atomic_read_membarrier_u64() in WaitLSNWakeup(). */
+	pg_atomic_write_membarrier_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
 }
 
 /*
@@ -325,10 +332,11 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
-	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 * "wake all waiters" (e.g., during promotion when recovery ends). Pairs
+	 * with pg_atomic_write_membarrier_u64() in updateMinWaitedLSN().
 	 */
 	if (XLogRecPtrIsValid(currentLSN) &&
-		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		pg_atomic_read_membarrier_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
@@ -450,8 +458,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (5.9K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/4-v5-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From 9cd16c4b02c0f6f91c5ab9d1e76ad1b0f288d725 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v5 5/7] Wake standby_write/standby_flush waiters from the WAL
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 26790fda5be..d2610cf0856 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -690,6 +690,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -744,6 +755,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0001-Use-barrier-semantics-when-reading-writing-writte.patch (3.1K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/5-v5-0001-Use-barrier-semantics-when-reading-writing-writte.patch)
  download | inline diff:
From 80b7d2d257dcf5a23a1851fd5bc71fe78bb604fc Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:45:52 +0800
Subject: [PATCH v5 1/7] Use barrier semantics when reading/writing writtenUpto

The walreceiver publishes its write position lock-free via writtenUpto.
On weakly-ordered architectures (ARM, PowerPC), both sides of this
handshake need explicit barriers so that the lock-less reader sees a
consistent state.

Use pg_atomic_write_membarrier_u64() at both write sites and
pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr().  This matches
the barrier semantics that GetWalRcvFlushRecPtr() and other LSN-position
functions get implicitly from their spinlock acquire/release, and
protects from bugs caused by expectations of similar barrier guarantees
from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiver.c      |  2 +-
 src/backend/replication/walreceiverfuncs.c | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 8185412a810..c0ac75b54d7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -975,7 +975,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	}
 
 	/* Update shared-memory status */
-	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
+	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..9447913c0e7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -321,7 +321,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 		walrcv->flushedUpto = recptr;
 		walrcv->receivedTLI = tli;
 		walrcv->latestChunkStart = recptr;
-		pg_atomic_write_u64(&walrcv->writtenUpto, recptr);
+		/* Pairs with pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr(). */
+		pg_atomic_write_membarrier_u64(&walrcv->writtenUpto, recptr);
 	}
 	walrcv->receiveStart = recptr;
 	walrcv->receiveStartTLI = tli;
@@ -363,14 +364,17 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use pg_atomic_read_membarrier_u64() to ensure that callers see up-to-date
+ * shared memory state, matching the barrier semantics provided by the
+ * spinlock in GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	return pg_atomic_read_membarrier_u64(&walrcv->writtenUpto);
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch (8.7K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/6-v5-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch)
  download | inline diff:
From 79e18b79cc0a037802903d852199d2fb7058df63 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:07:54 +0800
Subject: [PATCH v5 4/7] Use replay position as floor for WAIT FOR LSN
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 9ba785ea321..7b403c98dd0 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6a27183c207..18f78338330 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 0e74175f9eb..26790fda5be 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -674,4 +674,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v5-0006-Improve-WAIT-FOR-LSN-test-coverage.patch (12.6K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/7-v5-0006-Improve-WAIT-FOR-LSN-test-coverage.patch)
  download | inline diff:
From bd8c9e5bc05b32bafd3fa353923a652a0bc334ed Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 21 Apr 2026 11:19:02 +0800
Subject: [PATCH v5 6/7] Improve WAIT FOR LSN test coverage

Add regression coverage for several WAIT FOR LSN edge cases.

First, cover fresh walreceiver shared-memory initialization after a
standby restart.  Restart the standby while its upstream is down, so
RequestXLogStreaming() seeds writtenUpto/flushedUpto to the
segment-aligned receiveStart and the walreceiver cannot immediately
advance them.  Verify that the seeded flush position is segment-aligned,
that replay can be ahead of it, and that standby_write/standby_flush
still succeed for an already-replayed LSN via the replay-position floor
in GetCurrentLSNForWaitType().

Second, add fencepost checks for the target <= currentLSN predicate.
With replay paused and walreceiver stopped, verify exact boundaries for
standby_replay using pg_last_wal_replay_lsn(), and for standby_flush
using pg_last_wal_receive_lsn().  Also verify that a waiter for
current + 1 sleeps while replay is paused and wakes with success once
new WAL is delivered and replay advances.

Finally, add a cascading-standby timeline-switch test.  Start a waiter
on the downstream standby, promote its upstream, generate WAL on the new
timeline, and verify that the cascade follows the new timeline and the
wait completes successfully once replay reaches the target LSN.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
---
 src/test/recovery/t/049_wait_for_lsn.pl | 260 ++++++++++++++++++++++++
 1 file changed, 260 insertions(+)

diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index e259e643eb6..bef834fca31 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -44,6 +44,32 @@ sub resume_walreceiver
 		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
 }
 
+sub check_wait_for_lsn_fencepost
+{
+	my ($node, $mode, $current_lsn, $label) = @_;
+
+	my $lsn_minus = $node->safe_psql('postgres',
+		"SELECT ('$current_lsn'::pg_lsn - 1)::text");
+	my $lsn_plus = $node->safe_psql('postgres',
+		"SELECT ('$current_lsn'::pg_lsn + 1)::text");
+
+	foreach my $case (
+		[ $current_lsn, 'success', 'target == current succeeds', '5s' ],
+		[ $lsn_minus, 'success', 'target == current - 1 succeeds', '5s' ],
+		[ $lsn_plus, 'timeout', 'target == current + 1 times out', '500ms' ])
+	{
+		my ($target_lsn, $expected, $desc, $timeout) = @$case;
+		my $output = $node->safe_psql(
+			'postgres', qq[
+			WAIT FOR LSN '${target_lsn}'
+				WITH (MODE '$mode', timeout '$timeout', no_throw);]);
+
+		is($output, $expected, "$label: $desc");
+	}
+
+	return ($lsn_minus, $lsn_plus);
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -824,4 +850,238 @@ ok(1,
 $arc_standby->stop;
 $arc_primary->stop;
 
+# 10. Fresh-shmem walreceiver startup (29e7dbf5e4d).
+# RequestXLogStreaming() initializes writtenUpto/flushedUpto to the
+# segment-aligned receiveStart only when receiveStart was invalid.
+# Restart the standby with the primary stopped, so the walreceiver cannot
+# connect and advance these values past the initial one before we observe it.
+
+my $rcv_primary = PostgreSQL::Test::Cluster->new('rcv_primary');
+$rcv_primary->init(allows_streaming => 1);
+# No background WAL during our probes.
+$rcv_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"CREATE TABLE rcv_test AS SELECT generate_series(1,10) AS a");
+
+my $rcv_backup = 'rcv_backup';
+$rcv_primary->backup($rcv_backup);
+
+my $rcv_standby = PostgreSQL::Test::Cluster->new('rcv_standby');
+$rcv_standby->init_from_backup($rcv_primary, $rcv_backup, has_streaming => 1);
+$rcv_standby->start;
+
+# Switch WAL segments mid-stream so the replay ends mid-segment after the
+# upcoming standby restart.  That guarantees the initial value <
+# final replay LSN.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(11, 100))");
+$rcv_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(101, 110))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# Restart the standby with the primary down: WalRcvData is initialized, but
+# the walreceiver cannot connect and update writtenUpto/flushedUpto.  So,
+# the initial flushedUpto stays observable via pg_last_wal_receive_lsn().
+$rcv_standby->stop;
+$rcv_primary->stop;
+$rcv_standby->start;
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_last_wal_receive_lsn() IS NOT NULL;")
+  or die "walreceiver initial value did not become visible";
+
+# Freeze the replay so the (received, replay] window stays observable.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for rcv_standby replay to pause";
+
+my $rcv_receive =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $rcv_replay =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my $rcv_gap = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$rcv_replay'::pg_lsn, '$rcv_receive'::pg_lsn) > 0"
+);
+ok($rcv_gap eq 't',
+	"replay sits ahead of initial walreceiver flush position");
+
+my $rcv_receive_offset = $rcv_standby->safe_psql(
+	'postgres',
+	"SELECT mod(pg_wal_lsn_diff('$rcv_receive'::pg_lsn, '0/0'::pg_lsn),
+				setting::numeric)::int
+	   FROM pg_settings
+	  WHERE name = 'wal_segment_size'");
+is($rcv_receive_offset, '0',
+	"initial walreceiver flush position is segment-aligned");
+
+# WAIT FOR an $rcv_replay LSN succeeds in standby_write / standby_flush
+# modes thanks to GetCurrentLSNForWaitType() taking replay LSN as the floor.
+# We observe flushedUpto directly via pg_last_wal_receive_lsn().  writtenUpto
+# is covered indirectly: without the replay-position floor, standby_write would
+# wait at the seeded segment-start position and time out.
+foreach my $rcv_mode ('standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${rcv_replay}'
+			WITH (MODE '$rcv_mode', timeout '5s', no_throw);]);
+	ok($output eq "success",
+		"$rcv_mode succeeds for already-replayed LSN after standby restart");
+}
+
+# Restore primary and resume replay so section 11 can reuse the clusters.
+# Generate fresh WAL after reconnecting so the walreceiver advances its
+# flush position past the replay position before we freeze both frontiers.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(111, 120))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# 11. Off-by-one boundary checks for the wait predicate target <=
+# currentLSN.  Stop the walreceiver before pausing replay (stopping
+# after pause can hang -- see section 7d) so both replay and
+# walreceiver positions are frozen.
+stop_walreceiver($rcv_standby);
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for rcv_standby replay to pause";
+
+# 11a. standby_replay exact fencepost.  The replay position is frozen, so this
+# probes the standby_replay predicate directly.
+my $replay_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my (undef, $replay_lsn_plus) =
+  check_wait_for_lsn_fencepost($rcv_standby, 'standby_replay', $replay_lsn,
+	'standby_replay');
+
+# 11b. standby_flush exact fencepost.  pg_last_wal_receive_lsn() exposes the
+# flushed walreceiver position even after walreceiver exits, so this probes
+# the standby_flush predicate directly.  standby_write has no stable
+# SQL-visible boundary once walreceiver is stopped; it is covered by the
+# replay-floor and waiter wakeup tests above.
+my $flush_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $flush_covers_replay = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$flush_lsn'::pg_lsn, '$replay_lsn'::pg_lsn) >= 0"
+);
+ok($flush_covers_replay eq 't',
+	"standby_flush boundary is not masked by replay floor");
+
+check_wait_for_lsn_fencepost($rcv_standby, 'standby_flush', $flush_lsn,
+	'standby_flush');
+
+# 11c. A sleeping waiter at current + 1 wakes once replay advances
+# past it.  Start the waiter while replay is still paused so it is
+# guaranteed to sleep at replay_lsn_plus regardless of whether
+# flush_lsn > replay_lsn.  Then resume replay and restart the
+# walreceiver to deliver new WAL.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(200, 210))");
+
+my $boundary_session = $rcv_standby->background_psql('postgres');
+$boundary_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${replay_lsn_plus}'
+		WITH (MODE 'standby_replay', timeout '30s', no_throw);
+]);
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Boundary waiter did not sleep";
+
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+resume_walreceiver($rcv_standby);
+$boundary_session->quit;
+chomp($boundary_session->{stdout});
+is($boundary_session->{stdout},
+	'success',
+	"standby_replay: waiter at current + 1 wakes when replay advances");
+
+$rcv_standby->stop;
+$rcv_primary->stop;
+
+# 12. Timeline switch on a cascade standby.  A WAIT FOR LSN waiter on
+# a cascade standby must survive its upstream's promotion: the
+# cascade walreceiver reconnects on the new timeline and replay
+# continues across the boundary.
+
+my $tl_primary = PostgreSQL::Test::Cluster->new('tl_primary');
+$tl_primary->init(allows_streaming => 1);
+$tl_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$tl_primary->start;
+$tl_primary->safe_psql('postgres',
+	"CREATE TABLE tl_test AS SELECT generate_series(1, 10) AS a");
+
+my $tl_backup = 'tl_backup';
+$tl_primary->backup($tl_backup);
+
+my $tl_standby1 = PostgreSQL::Test::Cluster->new('tl_standby1');
+$tl_standby1->init_from_backup($tl_primary, $tl_backup, has_streaming => 1);
+$tl_standby1->start;
+
+# standby2 cascades from standby1.
+my $tl_backup2 = 'tl_backup2';
+$tl_standby1->backup($tl_backup2);
+
+my $tl_standby2 = PostgreSQL::Test::Cluster->new('tl_standby2');
+$tl_standby2->init_from_backup($tl_standby1, $tl_backup2, has_streaming => 1);
+$tl_standby2->start;
+
+$tl_primary->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(11, 20))");
+$tl_primary->wait_for_catchup($tl_standby1);
+$tl_standby1->wait_for_catchup($tl_standby2);
+
+# Target LSN well past current insert LSN, so reaching it requires
+# WAL produced on the new timeline.  Pause replay on standby2 to
+# guarantee the waiter is asleep when the switch happens.
+my $tl_target = $tl_primary->safe_psql('postgres',
+	"SELECT (pg_current_wal_insert_lsn() + 65536)::text");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$tl_standby2->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for tl_standby2 replay to pause";
+
+my $tl_session = $tl_standby2->background_psql('postgres');
+$tl_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${tl_target}'
+		WITH (MODE 'standby_replay', timeout '60s', no_throw);
+]);
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Cascade waiter did not sleep before promotion";
+
+# Promote standby1 to TLI 2; produce enough WAL on the new timeline
+# to push past tl_target and force a segment switch.
+$tl_standby1->promote;
+$tl_standby1->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(21, 1020))");
+$tl_standby1->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT received_tli > 1 FROM pg_stat_wal_receiver")
+  or die "tl_standby2 did not follow upstream timeline switch";
+
+$tl_session->quit;
+chomp($tl_session->{stdout});
+is($tl_session->{stdout}, 'success',
+	"WAIT FOR LSN survives upstream promotion and timeline switch on cascade standby"
+);
+
+$tl_standby2->stop;
+$tl_standby1->stop;
+$tl_primary->stop;
+
 done_testing();
-- 
2.51.0



  [application/octet-stream] v5-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch (1.9K, ../../CABPTF7WJ35p7uidJJZs7fzxBtbVL_0xSFUdZ2Fe8pXh00e=Mxw@mail.gmail.com/8-v5-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch)
  download | inline diff:
From cfdfc5903628e481d67ee51f91a316d89dea47cb Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 21 Apr 2026 11:43:52 +0800
Subject: [PATCH v5 7/7] Document that WAIT FOR LSN is timeline-blind

WAIT FOR LSN compares only the numeric LSN and has no notion of which
timeline a WAL record belongs to.  There are many possible scenarios when
timeline-switching can break read-your-writes consistency.  The proper
analysis and timeline support is possible in the next major release.  Yet
just document the current behaviour.

Reported-by: Xuneng Zhou <[email protected]>
Author: Alexander Korotkov <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 7b403c98dd0..916d8bce7ec 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -256,6 +256,20 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    timeline.
   </para>
 
+  <para>
+   <command>WAIT FOR</command> compares only the numeric
+   <acronym>LSN</acronym>; it has no notion of which timeline a WAL
+   record belongs to.  This matters when a standby continues recovery
+   across an upstream timeline switch &mdash; for example, a cascading
+   standby whose upstream gets promoted.  In that case
+   <command>WAIT FOR</command> will return <literal>success</literal>
+   as soon as the position used by the selected wait mode reaches or
+   passes the numeric <acronym>LSN</acronym>, regardless of which
+   timeline that <acronym>LSN</acronym> belongs to.  Applications that need to
+   confirm the target refers to the expected timeline must validate
+   the timeline themselves.
+  </para>
+
   <para>
    On a standby server, <command>WAIT FOR</command> sessions may be
    interrupted by recovery conflicts.  Some recovery conflicts are
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-04-28 21:01                                                                                                                                                                                   ` Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-04-28 21:01 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, Apr 21, 2026 at 7:03 AM Xuneng Zhou <[email protected]> wrote:
>
> On Tue, Apr 21, 2026 at 2:46 AM Alexander Korotkov <[email protected]> wrote:
>
> > The updated patchset is attached.  It includes improved coverage as
> > suggested by Andres upthread.  And documentation that WAIT FOR LSN is
> > timeline-blind (per off-list discussion with Xuneng).
>
> I revised the test patch 6 to make the new cases check the intended
> WAIT FOR behavior more directly, and to avoid cases where the test
> could pass for the wrong reason.
>
> The fresh walreceiver restart test now distinguishes what we can
> observe from what is only covered indirectly.
> 'pg_last_wal_receive_lsn()' reports 'flushedUpto', not 'writtenUpto',
> so the test now describes that state accurately and covers
> 'writtenUpto' through the 'standby_write' result. This seems
> appropriate to me since the two positions are seeded in the places and
> conditions. Test for flush lsn should also help verify write lsn.
>
> The fencepost tests were split by the actual frontier being tested.
> 'standby_replay' uses 'pg_last_wal_replay_lsn()', while
> 'standby_flush' uses 'pg_last_wal_receive_lsn()'. This avoids treating
> a replay-derived LSN as if it were also the exact write/flush
> boundary. I left 'standby_write' out of the exact fencepost helper
> because its frontier is not SQL-visible once walreceiver is stopped.
> The async wakeup case now starts the waiter while replay is still
> paused, so it must actually sleep before replay and walreceiver are
> allowed to advance.
>
> The cascading timeline-switch test now checks the 'WAIT FOR ...
> NO_THROW' status from background psql stdout. The previous log-marker
> pattern could pass after unexpected returned status, includingn
> 'timeout', because the following statement would still run. The
> 'received_tli > 1' check remains, but only as confirmation that the
> downstream followed the new timeline; the 'success' status proves the
> wait completed as intended.
>
> Please check it.

LGTM, I've added some comments for new functions in 0006.  I propose
to push this patchset.  Probably something is still missing and we
will have to go back to this.  But it seems to make a lot of aspects
much better.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v7-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (4.3K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/2-v7-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From 831440247580a08c35bd939241167b7b1750c2c9 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:59:13 +0800
Subject: [PATCH v7 2/7] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN
position then reads minWaitedLSN; the waiter stores its target into
minWaitedLSN then reads the position.  Without a barrier between each
side's store and load, a CPU may satisfy the load before the store
becomes globally visible, causing either side to miss a concurrent
update.  The result is a missed wakeup: the waiter sleeps indefinitely
until the next unrelated event.

Fix by embedding the required barriers into the atomic operations on
minWaitedLSN:

- In updateMinWaitedLSN(), use pg_atomic_write_membarrier_u64() so the
  waiter's preceding heap update is visible before the new minWaitedLSN
  value is published.

- In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
  fast-path check so the waker's preceding position store is globally
  visible before minWaitedLSN is read.

The waiter side is also covered by the barrier semantics already present
in GetCurrentLSNForWaitType(): GetWalRcvWriteRecPtr() uses an explicit
read barrier (from patch 0001), while the remaining getters acquire a
spinlock, which implies the same ordering.

Also call ResetLatch() unconditionally after WaitLatch(), following the
standard latch loop pattern.  WaitLatch() does not guarantee that all
simultaneously true wake conditions are reported in one return, so a
timeout can race with SetLatch().  If we skip ResetLatch() on a timeout
return, the code performs further asynchronous-state checks before
consuming the latch, violating the latch API's required wait/reset
pattern.  That can leave the latch set across loop exit and cause a
later unrelated WaitLatch() in the same backend to return immediately.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6a27183c207 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provide memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -184,7 +190,8 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
-	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+	/* Pairs with pg_atomic_read_membarrier_u64() in WaitLSNWakeup(). */
+	pg_atomic_write_membarrier_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
 }
 
 /*
@@ -325,10 +332,11 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
-	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 * "wake all waiters" (e.g., during promotion when recovery ends). Pairs
+	 * with pg_atomic_write_membarrier_u64() in updateMinWaitedLSN().
 	 */
 	if (XLogRecPtrIsValid(currentLSN) &&
-		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		pg_atomic_read_membarrier_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
@@ -450,8 +458,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (5.9K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/3-v7-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From d221dab12a358f74f29f5438cb55e9f95de66324 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v7 5/7] Wake standby_write/standby_flush waiters from the WAL
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 ++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 77 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 26790fda5be..d2610cf0856 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -690,6 +690,17 @@ $arc_primary->start;
 $arc_primary->safe_psql('postgres',
 	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
 
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+	'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+  BEGIN
+    RAISE LOG '%', msg;
+  END
+\$\$
+LANGUAGE plpgsql;
+]);
+
 my $arc_backup_name = 'arc_backup';
 $arc_primary->backup($arc_backup_name);
 
@@ -744,6 +755,72 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+	SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+	SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+	"standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch (8.7K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/4-v7-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch)
  download | inline diff:
From 769728161f29cb93b5eb83054bfb38add75227bd Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:07:54 +0800
Subject: [PATCH v7 4/7] Use replay position as floor for WAIT FOR LSN
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 29 ++++------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 9ba785ea321..7b403c98dd0 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6a27183c207..18f78338330 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 0e74175f9eb..26790fda5be 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -674,4 +674,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/5-v7-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From e3b4fb72b63d24ecd4807e5b561910f1fe3d73b6 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:05:46 +0800
Subject: [PATCH v7 3/7] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e39af79c03b..86c41bd3ae6 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 9e40f066c97..07eac07b9ce 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -981,12 +981,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1028,13 +1026,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0001-Use-barrier-semantics-when-reading-writing-writte.patch (3.1K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/6-v7-0001-Use-barrier-semantics-when-reading-writing-writte.patch)
  download | inline diff:
From e83a1d115d13f0ab3ff2a7ae844d95515bff2910 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:45:52 +0800
Subject: [PATCH v7 1/7] Use barrier semantics when reading/writing writtenUpto

The walreceiver publishes its write position lock-free via writtenUpto.
On weakly-ordered architectures (ARM, PowerPC), both sides of this
handshake need explicit barriers so that the lock-less reader sees a
consistent state.

Use pg_atomic_write_membarrier_u64() at both write sites and
pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr().  This matches
the barrier semantics that GetWalRcvFlushRecPtr() and other LSN-position
functions get implicitly from their spinlock acquire/release, and
protects from bugs caused by expectations of similar barrier guarantees
from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiver.c      |  2 +-
 src/backend/replication/walreceiverfuncs.c | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index df03dae5836..9e40f066c97 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,7 +978,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	}
 
 	/* Update shared-memory status */
-	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
+	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..9447913c0e7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -321,7 +321,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 		walrcv->flushedUpto = recptr;
 		walrcv->receivedTLI = tli;
 		walrcv->latestChunkStart = recptr;
-		pg_atomic_write_u64(&walrcv->writtenUpto, recptr);
+		/* Pairs with pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr(). */
+		pg_atomic_write_membarrier_u64(&walrcv->writtenUpto, recptr);
 	}
 	walrcv->receiveStart = recptr;
 	walrcv->receiveStartTLI = tli;
@@ -363,14 +364,17 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use pg_atomic_read_membarrier_u64() to ensure that callers see up-to-date
+ * shared memory state, matching the barrier semantics provided by the
+ * spinlock in GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	return pg_atomic_read_membarrier_u64(&walrcv->writtenUpto);
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0006-Improve-WAIT-FOR-LSN-test-coverage.patch (14.0K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/7-v7-0006-Improve-WAIT-FOR-LSN-test-coverage.patch)
  download | inline diff:
From 4dff19efaab036d60ec51095768cd82a2b50eefc Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Tue, 28 Apr 2026 23:56:46 +0300
Subject: [PATCH v7 6/7] Improve WAIT FOR LSN test coverage

Add regression coverage for several WAIT FOR LSN edge cases.

First, cover fresh walreceiver shared-memory initialization after a
standby restart.  Restart the standby while its upstream is down, so
RequestXLogStreaming() seeds writtenUpto/flushedUpto to the
segment-aligned receiveStart and the walreceiver cannot immediately
advance them.  Verify that the seeded flush position is segment-aligned,
that replay can be ahead of it, and that standby_write/standby_flush
still succeed for an already-replayed LSN via the replay-position floor
in GetCurrentLSNForWaitType().

Second, add fencepost checks for the target <= currentLSN predicate.
With replay paused and walreceiver stopped, verify exact boundaries for
standby_replay using pg_last_wal_replay_lsn(), and for standby_flush
using pg_last_wal_receive_lsn().  Also verify that a waiter for
current + 1 sleeps while replay is paused and wakes with success once
new WAL is delivered and replay advances.

Finally, add a cascading-standby timeline-switch test.  Start a waiter
on the downstream standby, promote its upstream, generate WAL on the new
timeline, and verify that the cascade follows the new timeline and the
wait completes successfully once replay reaches the target LSN.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
---
 src/test/recovery/t/049_wait_for_lsn.pl | 277 ++++++++++++++++++++++++
 1 file changed, 277 insertions(+)

diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index d2610cf0856..7f3f75cbbcc 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -12,6 +12,11 @@ use Test::More;
 # These allow us to stop WAL streaming so waiters block, then resume it.
 my $saved_primary_conninfo;
 
+# Stop the walreceiver on $node by clearing primary_conninfo and waiting
+# until pg_stat_wal_receiver becomes empty.  Used to freeze the
+# walreceiver-tracked positions (writtenUpto, flushedUpto) so a fencepost
+# test can rely on them not advancing.  The previous value is saved for
+# resume_walreceiver().
 sub stop_walreceiver
 {
 	my ($node) = @_;
@@ -31,6 +36,9 @@ sub stop_walreceiver
 		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
 }
 
+# Restart the walreceiver on $node by restoring primary_conninfo to the
+# value captured by stop_walreceiver() and waiting until walreceiver
+# reconnects.  Must be paired with a prior stop_walreceiver() call.
 sub resume_walreceiver
 {
 	my ($node) = @_;
@@ -44,6 +52,41 @@ sub resume_walreceiver
 		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
 }
 
+# Verify the wait predicate "target <= currentLSN" at the boundary.
+# Given $current_lsn (the frozen position for $mode), check that:
+#   target == current        -> success (predicate is <=)
+#   target == current - 1    -> success
+#   target == current + 1    -> timeout
+# The caller must ensure that the relevant LSN position on $node is
+# actually frozen (e.g. walreceiver stopped and replay paused), otherwise
+# the "+1" case may racily succeed.  Returns ($lsn_minus, $lsn_plus) so
+# the caller can reuse them, e.g. to drive an async wakeup test.
+sub check_wait_for_lsn_fencepost
+{
+	my ($node, $mode, $current_lsn, $label) = @_;
+
+	my $lsn_minus = $node->safe_psql('postgres',
+		"SELECT ('$current_lsn'::pg_lsn - 1)::text");
+	my $lsn_plus = $node->safe_psql('postgres',
+		"SELECT ('$current_lsn'::pg_lsn + 1)::text");
+
+	foreach my $case (
+		[ $current_lsn, 'success', 'target == current succeeds', '5s' ],
+		[ $lsn_minus, 'success', 'target == current - 1 succeeds', '5s' ],
+		[ $lsn_plus, 'timeout', 'target == current + 1 times out', '500ms' ])
+	{
+		my ($target_lsn, $expected, $desc, $timeout) = @$case;
+		my $output = $node->safe_psql(
+			'postgres', qq[
+			WAIT FOR LSN '${target_lsn}'
+				WITH (MODE '$mode', timeout '$timeout', no_throw);]);
+
+		is($output, $expected, "$label: $desc");
+	}
+
+	return ($lsn_minus, $lsn_plus);
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -824,4 +867,238 @@ ok(1,
 $arc_standby->stop;
 $arc_primary->stop;
 
+# 10. Fresh-shmem walreceiver startup (29e7dbf5e4d).
+# RequestXLogStreaming() initializes writtenUpto/flushedUpto to the
+# segment-aligned receiveStart only when receiveStart was invalid.
+# Restart the standby with the primary stopped, so the walreceiver cannot
+# connect and advance these values past the initial one before we observe it.
+
+my $rcv_primary = PostgreSQL::Test::Cluster->new('rcv_primary');
+$rcv_primary->init(allows_streaming => 1);
+# No background WAL during our probes.
+$rcv_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"CREATE TABLE rcv_test AS SELECT generate_series(1,10) AS a");
+
+my $rcv_backup = 'rcv_backup';
+$rcv_primary->backup($rcv_backup);
+
+my $rcv_standby = PostgreSQL::Test::Cluster->new('rcv_standby');
+$rcv_standby->init_from_backup($rcv_primary, $rcv_backup, has_streaming => 1);
+$rcv_standby->start;
+
+# Switch WAL segments mid-stream so the replay ends mid-segment after the
+# upcoming standby restart.  That guarantees the initial value <
+# final replay LSN.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(11, 100))");
+$rcv_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(101, 110))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# Restart the standby with the primary down: WalRcvData is initialized, but
+# the walreceiver cannot connect and update writtenUpto/flushedUpto.  So,
+# the initial flushedUpto stays observable via pg_last_wal_receive_lsn().
+$rcv_standby->stop;
+$rcv_primary->stop;
+$rcv_standby->start;
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_last_wal_receive_lsn() IS NOT NULL;")
+  or die "walreceiver initial value did not become visible";
+
+# Freeze the replay so the (received, replay] window stays observable.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for rcv_standby replay to pause";
+
+my $rcv_receive =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $rcv_replay =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my $rcv_gap = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$rcv_replay'::pg_lsn, '$rcv_receive'::pg_lsn) > 0"
+);
+ok($rcv_gap eq 't',
+	"replay sits ahead of initial walreceiver flush position");
+
+my $rcv_receive_offset = $rcv_standby->safe_psql(
+	'postgres',
+	"SELECT mod(pg_wal_lsn_diff('$rcv_receive'::pg_lsn, '0/0'::pg_lsn),
+				setting::numeric)::int
+	   FROM pg_settings
+	  WHERE name = 'wal_segment_size'");
+is($rcv_receive_offset, '0',
+	"initial walreceiver flush position is segment-aligned");
+
+# WAIT FOR an $rcv_replay LSN succeeds in standby_write / standby_flush
+# modes thanks to GetCurrentLSNForWaitType() taking replay LSN as the floor.
+# We observe flushedUpto directly via pg_last_wal_receive_lsn().  writtenUpto
+# is covered indirectly: without the replay-position floor, standby_write would
+# wait at the seeded segment-start position and time out.
+foreach my $rcv_mode ('standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${rcv_replay}'
+			WITH (MODE '$rcv_mode', timeout '5s', no_throw);]);
+	ok($output eq "success",
+		"$rcv_mode succeeds for already-replayed LSN after standby restart");
+}
+
+# Restore primary and resume replay so section 11 can reuse the clusters.
+# Generate fresh WAL after reconnecting so the walreceiver advances its
+# flush position past the replay position before we freeze both frontiers.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(111, 120))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# 11. Off-by-one boundary checks for the wait predicate target <=
+# currentLSN.  Stop the walreceiver before pausing replay (stopping
+# after pause can hang -- see section 7d) so both replay and
+# walreceiver positions are frozen.
+stop_walreceiver($rcv_standby);
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for rcv_standby replay to pause";
+
+# 11a. standby_replay exact fencepost.  The replay position is frozen, so this
+# probes the standby_replay predicate directly.
+my $replay_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my (undef, $replay_lsn_plus) =
+  check_wait_for_lsn_fencepost($rcv_standby, 'standby_replay', $replay_lsn,
+	'standby_replay');
+
+# 11b. standby_flush exact fencepost.  pg_last_wal_receive_lsn() exposes the
+# flushed walreceiver position even after walreceiver exits, so this probes
+# the standby_flush predicate directly.  standby_write has no stable
+# SQL-visible boundary once walreceiver is stopped; it is covered by the
+# replay-floor and waiter wakeup tests above.
+my $flush_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $flush_covers_replay = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$flush_lsn'::pg_lsn, '$replay_lsn'::pg_lsn) >= 0"
+);
+ok($flush_covers_replay eq 't',
+	"standby_flush boundary is not masked by replay floor");
+
+check_wait_for_lsn_fencepost($rcv_standby, 'standby_flush', $flush_lsn,
+	'standby_flush');
+
+# 11c. A sleeping waiter at current + 1 wakes once replay advances
+# past it.  Start the waiter while replay is still paused so it is
+# guaranteed to sleep at replay_lsn_plus regardless of whether
+# flush_lsn > replay_lsn.  Then resume replay and restart the
+# walreceiver to deliver new WAL.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(200, 210))");
+
+my $boundary_session = $rcv_standby->background_psql('postgres');
+$boundary_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${replay_lsn_plus}'
+		WITH (MODE 'standby_replay', timeout '30s', no_throw);
+]);
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Boundary waiter did not sleep";
+
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+resume_walreceiver($rcv_standby);
+$boundary_session->quit;
+chomp($boundary_session->{stdout});
+is($boundary_session->{stdout},
+	'success',
+	"standby_replay: waiter at current + 1 wakes when replay advances");
+
+$rcv_standby->stop;
+$rcv_primary->stop;
+
+# 12. Timeline switch on a cascade standby.  A WAIT FOR LSN waiter on
+# a cascade standby must survive its upstream's promotion: the
+# cascade walreceiver reconnects on the new timeline and replay
+# continues across the boundary.
+
+my $tl_primary = PostgreSQL::Test::Cluster->new('tl_primary');
+$tl_primary->init(allows_streaming => 1);
+$tl_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$tl_primary->start;
+$tl_primary->safe_psql('postgres',
+	"CREATE TABLE tl_test AS SELECT generate_series(1, 10) AS a");
+
+my $tl_backup = 'tl_backup';
+$tl_primary->backup($tl_backup);
+
+my $tl_standby1 = PostgreSQL::Test::Cluster->new('tl_standby1');
+$tl_standby1->init_from_backup($tl_primary, $tl_backup, has_streaming => 1);
+$tl_standby1->start;
+
+# standby2 cascades from standby1.
+my $tl_backup2 = 'tl_backup2';
+$tl_standby1->backup($tl_backup2);
+
+my $tl_standby2 = PostgreSQL::Test::Cluster->new('tl_standby2');
+$tl_standby2->init_from_backup($tl_standby1, $tl_backup2, has_streaming => 1);
+$tl_standby2->start;
+
+$tl_primary->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(11, 20))");
+$tl_primary->wait_for_catchup($tl_standby1);
+$tl_standby1->wait_for_catchup($tl_standby2);
+
+# Target LSN well past current insert LSN, so reaching it requires
+# WAL produced on the new timeline.  Pause replay on standby2 to
+# guarantee the waiter is asleep when the switch happens.
+my $tl_target = $tl_primary->safe_psql('postgres',
+	"SELECT (pg_current_wal_insert_lsn() + 65536)::text");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$tl_standby2->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for tl_standby2 replay to pause";
+
+my $tl_session = $tl_standby2->background_psql('postgres');
+$tl_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${tl_target}'
+		WITH (MODE 'standby_replay', timeout '60s', no_throw);
+]);
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Cascade waiter did not sleep before promotion";
+
+# Promote standby1 to TLI 2; produce enough WAL on the new timeline
+# to push past tl_target and force a segment switch.
+$tl_standby1->promote;
+$tl_standby1->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(21, 1020))");
+$tl_standby1->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT received_tli > 1 FROM pg_stat_wal_receiver")
+  or die "tl_standby2 did not follow upstream timeline switch";
+
+$tl_session->quit;
+chomp($tl_session->{stdout});
+is($tl_session->{stdout}, 'success',
+	"WAIT FOR LSN survives upstream promotion and timeline switch on cascade standby"
+);
+
+$tl_standby2->stop;
+$tl_standby1->stop;
+$tl_primary->stop;
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch (1.9K, ../../CAPpHfdufWG032J=fyv1eWoveeyPwqJ57PGU2edA5OsOmexGDTw@mail.gmail.com/8-v7-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch)
  download | inline diff:
From 2903f43bddbc5ae1a7832aed8c63abbf7c052fe8 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 21 Apr 2026 11:43:52 +0800
Subject: [PATCH v7 7/7] Document that WAIT FOR LSN is timeline-blind

WAIT FOR LSN compares only the numeric LSN and has no notion of which
timeline a WAL record belongs to.  There are many possible scenarios when
timeline-switching can break read-your-writes consistency.  The proper
analysis and timeline support is possible in the next major release.  Yet
just document the current behaviour.

Reported-by: Xuneng Zhou <[email protected]>
Author: Alexander Korotkov <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 7b403c98dd0..916d8bce7ec 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -256,6 +256,20 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    timeline.
   </para>
 
+  <para>
+   <command>WAIT FOR</command> compares only the numeric
+   <acronym>LSN</acronym>; it has no notion of which timeline a WAL
+   record belongs to.  This matters when a standby continues recovery
+   across an upstream timeline switch &mdash; for example, a cascading
+   standby whose upstream gets promoted.  In that case
+   <command>WAIT FOR</command> will return <literal>success</literal>
+   as soon as the position used by the selected wait mode reaches or
+   passes the numeric <acronym>LSN</acronym>, regardless of which
+   timeline that <acronym>LSN</acronym> belongs to.  Applications that need to
+   confirm the target refers to the expected timeline must validate
+   the timeline themselves.
+  </para>
+
   <para>
    On a standby server, <command>WAIT FOR</command> sessions may be
    interrupted by recovery conflicts.  Some recovery conflicts are
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-05-01 02:44                                                                                                                                                                                     ` Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-01 02:44 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Wed, Apr 29, 2026 at 5:01 AM Alexander Korotkov <[email protected]>
wrote:

> On Tue, Apr 21, 2026 at 7:03 AM Xuneng Zhou <[email protected]> wrote:
> >
> > On Tue, Apr 21, 2026 at 2:46 AM Alexander Korotkov <[email protected]>
> wrote:
> >
> > > The updated patchset is attached.  It includes improved coverage as
> > > suggested by Andres upthread.  And documentation that WAIT FOR LSN is
> > > timeline-blind (per off-list discussion with Xuneng).
> >
> > I revised the test patch 6 to make the new cases check the intended
> > WAIT FOR behavior more directly, and to avoid cases where the test
> > could pass for the wrong reason.
> >
> > The fresh walreceiver restart test now distinguishes what we can
> > observe from what is only covered indirectly.
> > 'pg_last_wal_receive_lsn()' reports 'flushedUpto', not 'writtenUpto',
> > so the test now describes that state accurately and covers
> > 'writtenUpto' through the 'standby_write' result. This seems
> > appropriate to me since the two positions are seeded in the places and
> > conditions. Test for flush lsn should also help verify write lsn.
> >
> > The fencepost tests were split by the actual frontier being tested.
> > 'standby_replay' uses 'pg_last_wal_replay_lsn()', while
> > 'standby_flush' uses 'pg_last_wal_receive_lsn()'. This avoids treating
> > a replay-derived LSN as if it were also the exact write/flush
> > boundary. I left 'standby_write' out of the exact fencepost helper
> > because its frontier is not SQL-visible once walreceiver is stopped.
> > The async wakeup case now starts the waiter while replay is still
> > paused, so it must actually sleep before replay and walreceiver are
> > allowed to advance.
> >
> > The cascading timeline-switch test now checks the 'WAIT FOR ...
> > NO_THROW' status from background psql stdout. The previous log-marker
> > pattern could pass after unexpected returned status, includingn
> > 'timeout', because the following statement would still run. The
> > 'received_tli > 1' check remains, but only as confirmation that the
> > downstream followed the new timeline; the 'success' status proves the
> > wait completed as intended.
> >
> > Please check it.
>
> LGTM, I've added some comments for new functions in 0006.  I propose
> to push this patchset.  Probably something is still missing and we
> will have to go back to this.  But it seems to make a lot of aspects
> much better.
>

I reviewed the patchset and found a potential issue in the test for patch
5, similar to the log-checking problem in the cascading timeline-switch
test. I've applied a minor fix to address it. Other parts LGTM.

Best,
Xuneng


Attachments:

  [application/octet-stream] v8-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch (9.8K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/3-v8-0004-Use-replay-position-as-floor-for-WAIT-FOR-LSN-sta.patch)
  download | inline diff:
From b13f59ce14d06837f4c15aa85417584c34899dc3 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:07:54 +0800
Subject: [PATCH v8 4/7] Use replay position as floor for WAIT FOR LSN
 standby_(write|flush)

GetCurrentLSNForWaitType() for standby_write and standby_flush modes
returned only the walreceiver position, which may lag behind WAL
already present on the standby from a base backup, archive restore,
or prior streaming.  This could cause unnecessary blocking if the
target LSN falls between the walreceiver's tracked position and the
replay position.

Fix by returning the maximum of the walreceiver position and the
replay position.  WAL up to the replay point is physically on disk
regardless of its origin, so there is no reason to wait for the
walreceiver to re-receive it.

This complements 29e7dbf5e4d, which seeded writtenUpto to
receiveStart in RequestXLogStreaming() to fix the most common
hang scenario.  The getter-level floor handles the remaining edge
cases: targets between receiveStart and the replay position, and
standbys running with archive recovery only (no walreceiver).

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml          | 38 ++++++-------
 src/backend/access/transam/xlogwait.c   | 21 ++++++-
 src/test/recovery/t/049_wait_for_lsn.pl | 73 +++++++++++++++++++++++++
 3 files changed, 109 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 9ba785ea321..8819973c774 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -105,30 +105,25 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
           <listitem>
            <para>
             <literal>standby_write</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and written to disk on a
-            standby server, but not yet flushed. This is faster than
+            LSN to be written to disk on a standby server, but not yet
+            necessarily flushed.  This is faster than
             <literal>standby_flush</literal> but provides weaker durability
             guarantees since the data may still be in operating system
-            buffers. After successful completion, the
-            <structfield>written_lsn</structfield> column in
-            <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link> will show
-            a value greater than or equal to the target LSN. This mode can
-            only be used during recovery.
+            buffers.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
            <para>
             <literal>standby_flush</literal>: Wait for the WAL containing the
-            LSN to be received from the primary and flushed to disk on a
-            standby server. This provides a durability guarantee without
-            waiting for the WAL to be applied. After successful completion,
-            <function>pg_last_wal_receive_lsn()</function> will return a
-            value greater than or equal to the target LSN. This value is
-            also available as the <structfield>flushed_lsn</structfield>
-            column in <link linkend="monitoring-pg-stat-wal-receiver-view">
-            <structname>pg_stat_wal_receiver</structname></link>. This mode
-            can only be used during recovery.
+            LSN to be flushed to disk on a standby server.  This provides
+            a durability guarantee without waiting for the WAL to be
+            applied.  This is satisfied by WAL already present on the
+            standby from a base backup, archive restore, or prior
+            streaming, as well as WAL newly received from the primary.
+            This mode can only be used during recovery.
            </para>
           </listitem>
           <listitem>
@@ -238,10 +233,11 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    useful to achieve read-your-writes consistency while using an async
    replica for reads and the primary for writes. The
    <literal>standby_flush</literal> mode waits for the WAL to be flushed
-   to durable storage on the replica, providing a durability guarantee
-   without waiting for replay. The <literal>standby_write</literal> mode
-   waits for the WAL to be written to the operating system, which is
-   faster than flush but provides weaker durability guarantees. The
+   to durable storage on the replica, or to have already been replayed
+   from WAL present on the standby. The <literal>standby_write</literal> mode
+   waits for the WAL to be written to the operating system, or to have
+   already been replayed, which is faster than flush for newly received
+   WAL but provides weaker durability guarantees. The
    <literal>primary_flush</literal> mode waits for WAL to be flushed on
    a primary server. In all cases, the <acronym>LSN</acronym> of the last
    modification should be stored on the client application side or the
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 6a27183c207..18f78338330 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -111,10 +111,27 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 			return GetXLogReplayRecPtr(NULL);
 
 		case WAIT_LSN_TYPE_STANDBY_WRITE:
-			return GetWalRcvWriteRecPtr();
+			{
+				XLogRecPtr	recptr = GetWalRcvWriteRecPtr();
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/*
+				 * Use the replay position as a floor.  WAL up to the replay
+				 * point is already on disk from a base backup, archive
+				 * restore, or prior streaming, so there is no reason to wait
+				 * for the walreceiver to re-receive it.
+				 */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_STANDBY_FLUSH:
-			return GetWalRcvFlushRecPtr(NULL, NULL);
+			{
+				XLogRecPtr	recptr = GetWalRcvFlushRecPtr(NULL, NULL);
+				XLogRecPtr	replay = GetXLogReplayRecPtr(NULL);
+
+				/* Same floor as standby_write; see comment above. */
+				return Max(recptr, replay);
+			}
 
 		case WAIT_LSN_TYPE_PRIMARY_FLUSH:
 			return GetFlushRecPtr(NULL);
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 0e74175f9eb..26790fda5be 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -674,4 +674,77 @@ for (my $i = 0; $i < 3; $i++)
 	$wait_sessions[$i]->{run}->finish;
 }
 
+# 9. Archive-only standby tests: verify standby_write/standby_flush work
+# without a walreceiver.  These exercises the replay-position floor in
+# GetCurrentLSNForWaitType().
+#
+# We set up a separate primary with archiving and an archive-only standby
+# (has_restoring, no has_streaming), so no walreceiver ever starts and the
+# shared walreceiver positions (writtenUpto, flushedUpto) stay at their
+# zero-initialized values.
+
+my $arc_primary = PostgreSQL::Test::Cluster->new('arc_primary');
+$arc_primary->init(has_archiving => 1, allows_streaming => 1);
+$arc_primary->start;
+
+$arc_primary->safe_psql('postgres',
+	"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+
+my $arc_backup_name = 'arc_backup';
+$arc_primary->backup($arc_backup_name);
+
+# Generate WAL that will be archived and replayed on the standby.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(11, 20))");
+my $arc_target_lsn =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+# Force WAL to be archived by switching segments, then wait for archiving.
+my $arc_segment = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment' FROM pg_stat_archiver}, 't')
+  or die "Timed out waiting for WAL archiving on arc_primary";
+
+# Create an archive-only standby: has_restoring but NOT has_streaming.
+# No primary_conninfo means no walreceiver will start.
+my $arc_standby = PostgreSQL::Test::Cluster->new('arc_standby');
+$arc_standby->init_from_backup($arc_primary, $arc_backup_name,
+	has_restoring => 1);
+$arc_standby->start;
+
+# Wait for the standby to replay past our target LSN via archive recovery.
+$arc_standby->poll_query_until('postgres',
+	qq{SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$arc_target_lsn') >= 0}
+) or die "Timed out waiting for archive replay on arc_standby";
+
+# Sanity: verify no walreceiver is running.
+$output = $arc_standby->safe_psql('postgres',
+	"SELECT count(*) FROM pg_stat_wal_receiver");
+is($output, '0', "arc_standby has no walreceiver");
+
+# 9a. Getter fallback: standby_write/standby_flush succeed immediately when
+# the target LSN has already been replayed, even though writtenUpto and
+# flushedUpto are zero.  GetCurrentLSNForWaitType() returns
+# Max(walrcv_pos, replay), so replay >= target satisfies the check on the
+# first loop iteration without ever sleeping.
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_write', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_write succeeds on archive-only standby (getter fallback)");
+
+$output = $arc_standby->safe_psql(
+	'postgres', qq[
+	WAIT FOR LSN '${arc_target_lsn}'
+		WITH (MODE 'standby_flush', timeout '3s', no_throw);]);
+ok($output eq "success",
+	"standby_flush succeeds on archive-only standby (getter fallback)");
+
+$arc_standby->stop;
+$arc_primary->stop;
+
 done_testing();
-- 
2.51.0



  [application/octet-stream] v8-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch (1.9K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/4-v8-0007-Document-that-WAIT-FOR-LSN-is-timeline-blind.patch)
  download | inline diff:
From 79589ef643c698a73055324805141ecbda588091 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 21 Apr 2026 11:43:52 +0800
Subject: [PATCH v8 7/7] Document that WAIT FOR LSN is timeline-blind

WAIT FOR LSN compares only the numeric LSN and has no notion of which
timeline a WAL record belongs to.  There are many possible scenarios when
timeline-switching can break read-your-writes consistency.  The proper
analysis and timeline support is possible in the next major release.  Yet
just document the current behaviour.

Reported-by: Xuneng Zhou <[email protected]>
Author: Alexander Korotkov <[email protected]>
---
 doc/src/sgml/ref/wait_for.sgml | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/doc/src/sgml/ref/wait_for.sgml b/doc/src/sgml/ref/wait_for.sgml
index 8819973c774..cd5dd031991 100644
--- a/doc/src/sgml/ref/wait_for.sgml
+++ b/doc/src/sgml/ref/wait_for.sgml
@@ -257,6 +257,20 @@ WAIT FOR LSN '<replaceable class="parameter">lsn</replaceable>'
    timeline.
   </para>
 
+  <para>
+   <command>WAIT FOR</command> compares only the numeric
+   <acronym>LSN</acronym>; it has no notion of which timeline a WAL
+   record belongs to.  This matters when a standby continues recovery
+   across an upstream timeline switch &mdash; for example, a cascading
+   standby whose upstream gets promoted.  In that case
+   <command>WAIT FOR</command> will return <literal>success</literal>
+   as soon as the position used by the selected wait mode reaches or
+   passes the numeric <acronym>LSN</acronym>, regardless of which
+   timeline that <acronym>LSN</acronym> belongs to.  Applications that need to
+   confirm the target refers to the expected timeline must validate
+   the timeline themselves.
+  </para>
+
   <para>
    On a standby server, <command>WAIT FOR</command> sessions may be
    interrupted by recovery conflicts.  Some recovery conflicts are
-- 
2.51.0



  [application/octet-stream] v8-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch (5.2K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/5-v8-0003-Remove-redundant-WAIT-FOR-LSN-caller-side-pre-che.patch)
  download | inline diff:
From ee45997619df3712e87346146e8ae98df585a8c2 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:05:46 +0800
Subject: [PATCH v8 3/7] Remove redundant WAIT FOR LSN caller-side pre-checks

All five wakeup call sites duplicate WaitLSNWakeup()'s internal
fast-path minWaitedLSN check and add an unnecessary NULL check
on waitLSNState.

Remove the inline pre-checks and call WaitLSNWakeup() directly.
The fast-path check inside WaitLSNWakeup() already returns early
when no waiter's target has been reached, so there is no
performance difference.

The waitLSNState NULL checks are also unnecessary: shared memory
is fully initialized before any backend or auxiliary process
starts, so waitLSNState is always non-NULL at these call sites.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/jzq5shdewncpxc35r3s2mcfsmo4bjovkza5mnqf5bdfumhfi3g%40bglckf7dxmw5
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlog.c         | 16 ++++++----------
 src/backend/access/transam/xlogrecovery.c | 11 ++++-------
 src/backend/replication/walreceiver.c     | 17 ++++++-----------
 3 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f85b5286086..b5801ab663e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2936,12 +2936,10 @@ XLogFlush(XLogRecPtr record)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * If we still haven't flushed to the request point then we have a
@@ -3126,12 +3124,10 @@ XLogBackgroundFlush(void)
 	WalSndWakeupProcessRequests(true, !RecoveryInProgress());
 
 	/*
-	 * If we flushed an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for primary flush LSN to reach current flush
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogwrtResult.Flush >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_PRIMARY_FLUSH])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
+	WaitLSNWakeup(WAIT_LSN_TYPE_PRIMARY_FLUSH, LogwrtResult.Flush);
 
 	/*
 	 * Great, done. To take some work off the critical path, try to initialize
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..4f2eaa36990 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,14 +1782,11 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * Wake up processes waiting for standby replay LSN to reach
+			 * current replay position.
 			 */
-			if (waitLSNState &&
-				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
-				 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_REPLAY])))
-				WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c0ac75b54d7..8ed796f3033 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -978,12 +978,10 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
-	 * If we wrote an LSN that someone was waiting for, notify the waiters.
+	 * Wake up processes waiting for standby write LSN to reach current write
+	 * position.
 	 */
-	if (waitLSNState &&
-		(LogstreamResult.Write >=
-		 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE])))
-		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
+	WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, LogstreamResult.Write);
 
 	/*
 	 * Close the current segment if it's fully written up in the last cycle of
@@ -1025,13 +1023,10 @@ XLogWalRcvFlush(bool dying, TimeLineID tli)
 		SpinLockRelease(&walrcv->mutex);
 
 		/*
-		 * If we flushed an LSN that someone was waiting for, notify the
-		 * waiters.
+		 * Wake up processes waiting for standby flush LSN to reach current
+		 * flush position.
 		 */
-		if (waitLSNState &&
-			(LogstreamResult.Flush >=
-			 pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_FLUSH])))
-			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
+		WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, LogstreamResult.Flush);
 
 		/* Signal the startup process and walsender that new WAL has arrived */
 		WakeupRecovery();
-- 
2.51.0



  [application/octet-stream] v8-0006-Improve-WAIT-FOR-LSN-test-coverage.patch (14.0K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/6-v8-0006-Improve-WAIT-FOR-LSN-test-coverage.patch)
  download | inline diff:
From 4e7bcf90735b34d5a6755548103a1c396cd73d49 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Tue, 28 Apr 2026 23:56:46 +0300
Subject: [PATCH v8 6/7] Improve WAIT FOR LSN test coverage

Add regression coverage for several WAIT FOR LSN edge cases.

First, cover fresh walreceiver shared-memory initialization after a
standby restart.  Restart the standby while its upstream is down, so
RequestXLogStreaming() seeds writtenUpto/flushedUpto to the
segment-aligned receiveStart and the walreceiver cannot immediately
advance them.  Verify that the seeded flush position is segment-aligned,
that replay can be ahead of it, and that standby_write/standby_flush
still succeed for an already-replayed LSN via the replay-position floor
in GetCurrentLSNForWaitType().

Second, add fencepost checks for the target <= currentLSN predicate.
With replay paused and walreceiver stopped, verify exact boundaries for
standby_replay using pg_last_wal_replay_lsn(), and for standby_flush
using pg_last_wal_receive_lsn().  Also verify that a waiter for
current + 1 sleeps while replay is paused and wakes with success once
new WAL is delivered and replay advances.

Finally, add a cascading-standby timeline-switch test.  Start a waiter
on the downstream standby, promote its upstream, generate WAL on the new
timeline, and verify that the cascade follows the new timeline and the
wait completes successfully once replay reaches the target LSN.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Alexander Korotkov <[email protected]>
Author: Xuneng Zhou <[email protected]>
---
 src/test/recovery/t/049_wait_for_lsn.pl | 277 ++++++++++++++++++++++++
 1 file changed, 277 insertions(+)

diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 72dbe06b108..db3dda43b90 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -12,6 +12,11 @@ use Test::More;
 # These allow us to stop WAL streaming so waiters block, then resume it.
 my $saved_primary_conninfo;
 
+# Stop the walreceiver on $node by clearing primary_conninfo and waiting
+# until pg_stat_wal_receiver becomes empty.  Used to freeze the
+# walreceiver-tracked positions (writtenUpto, flushedUpto) so a fencepost
+# test can rely on them not advancing.  The previous value is saved for
+# resume_walreceiver().
 sub stop_walreceiver
 {
 	my ($node) = @_;
@@ -31,6 +36,9 @@ sub stop_walreceiver
 		"SELECT NOT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
 }
 
+# Restart the walreceiver on $node by restoring primary_conninfo to the
+# value captured by stop_walreceiver() and waiting until walreceiver
+# reconnects.  Must be paired with a prior stop_walreceiver() call.
 sub resume_walreceiver
 {
 	my ($node) = @_;
@@ -44,6 +52,41 @@ sub resume_walreceiver
 		"SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);");
 }
 
+# Verify the wait predicate "target <= currentLSN" at the boundary.
+# Given $current_lsn (the frozen position for $mode), check that:
+#   target == current        -> success (predicate is <=)
+#   target == current - 1    -> success
+#   target == current + 1    -> timeout
+# The caller must ensure that the relevant LSN position on $node is
+# actually frozen (e.g. walreceiver stopped and replay paused), otherwise
+# the "+1" case may racily succeed.  Returns ($lsn_minus, $lsn_plus) so
+# the caller can reuse them, e.g. to drive an async wakeup test.
+sub check_wait_for_lsn_fencepost
+{
+	my ($node, $mode, $current_lsn, $label) = @_;
+
+	my $lsn_minus = $node->safe_psql('postgres',
+		"SELECT ('$current_lsn'::pg_lsn - 1)::text");
+	my $lsn_plus = $node->safe_psql('postgres',
+		"SELECT ('$current_lsn'::pg_lsn + 1)::text");
+
+	foreach my $case (
+		[ $current_lsn, 'success', 'target == current succeeds', '5s' ],
+		[ $lsn_minus, 'success', 'target == current - 1 succeeds', '5s' ],
+		[ $lsn_plus, 'timeout', 'target == current + 1 times out', '500ms' ])
+	{
+		my ($target_lsn, $expected, $desc, $timeout) = @$case;
+		my $output = $node->safe_psql(
+			'postgres', qq[
+			WAIT FOR LSN '${target_lsn}'
+				WITH (MODE '$mode', timeout '$timeout', no_throw);]);
+
+		is($output, $expected, "$label: $desc");
+	}
+
+	return ($lsn_minus, $lsn_plus);
+}
+
 # Initialize primary node
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
@@ -811,4 +854,238 @@ is($arc_flush_session->{stdout},
 $arc_standby->stop;
 $arc_primary->stop;
 
+# 10. Fresh-shmem walreceiver startup (29e7dbf5e4d).
+# RequestXLogStreaming() initializes writtenUpto/flushedUpto to the
+# segment-aligned receiveStart only when receiveStart was invalid.
+# Restart the standby with the primary stopped, so the walreceiver cannot
+# connect and advance these values past the initial one before we observe it.
+
+my $rcv_primary = PostgreSQL::Test::Cluster->new('rcv_primary');
+$rcv_primary->init(allows_streaming => 1);
+# No background WAL during our probes.
+$rcv_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"CREATE TABLE rcv_test AS SELECT generate_series(1,10) AS a");
+
+my $rcv_backup = 'rcv_backup';
+$rcv_primary->backup($rcv_backup);
+
+my $rcv_standby = PostgreSQL::Test::Cluster->new('rcv_standby');
+$rcv_standby->init_from_backup($rcv_primary, $rcv_backup, has_streaming => 1);
+$rcv_standby->start;
+
+# Switch WAL segments mid-stream so the replay ends mid-segment after the
+# upcoming standby restart.  That guarantees the initial value <
+# final replay LSN.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(11, 100))");
+$rcv_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(101, 110))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# Restart the standby with the primary down: WalRcvData is initialized, but
+# the walreceiver cannot connect and update writtenUpto/flushedUpto.  So,
+# the initial flushedUpto stays observable via pg_last_wal_receive_lsn().
+$rcv_standby->stop;
+$rcv_primary->stop;
+$rcv_standby->start;
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_last_wal_receive_lsn() IS NOT NULL;")
+  or die "walreceiver initial value did not become visible";
+
+# Freeze the replay so the (received, replay] window stays observable.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for rcv_standby replay to pause";
+
+my $rcv_receive =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $rcv_replay =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my $rcv_gap = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$rcv_replay'::pg_lsn, '$rcv_receive'::pg_lsn) > 0"
+);
+ok($rcv_gap eq 't',
+	"replay sits ahead of initial walreceiver flush position");
+
+my $rcv_receive_offset = $rcv_standby->safe_psql(
+	'postgres',
+	"SELECT mod(pg_wal_lsn_diff('$rcv_receive'::pg_lsn, '0/0'::pg_lsn),
+				setting::numeric)::int
+	   FROM pg_settings
+	  WHERE name = 'wal_segment_size'");
+is($rcv_receive_offset, '0',
+	"initial walreceiver flush position is segment-aligned");
+
+# WAIT FOR an $rcv_replay LSN succeeds in standby_write / standby_flush
+# modes thanks to GetCurrentLSNForWaitType() taking replay LSN as the floor.
+# We observe flushedUpto directly via pg_last_wal_receive_lsn().  writtenUpto
+# is covered indirectly: without the replay-position floor, standby_write would
+# wait at the seeded segment-start position and time out.
+foreach my $rcv_mode ('standby_write', 'standby_flush')
+{
+	$output = $rcv_standby->safe_psql(
+		'postgres', qq[
+		WAIT FOR LSN '${rcv_replay}'
+			WITH (MODE '$rcv_mode', timeout '5s', no_throw);]);
+	ok($output eq "success",
+		"$rcv_mode succeeds for already-replayed LSN after standby restart");
+}
+
+# Restore primary and resume replay so section 11 can reuse the clusters.
+# Generate fresh WAL after reconnecting so the walreceiver advances its
+# flush position past the replay position before we freeze both frontiers.
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+$rcv_primary->start;
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(111, 120))");
+$rcv_primary->wait_for_catchup($rcv_standby);
+
+# 11. Off-by-one boundary checks for the wait predicate target <=
+# currentLSN.  Stop the walreceiver before pausing replay (stopping
+# after pause can hang -- see section 7d) so both replay and
+# walreceiver positions are frozen.
+stop_walreceiver($rcv_standby);
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$rcv_standby->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for rcv_standby replay to pause";
+
+# 11a. standby_replay exact fencepost.  The replay position is frozen, so this
+# probes the standby_replay predicate directly.
+my $replay_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+my (undef, $replay_lsn_plus) =
+  check_wait_for_lsn_fencepost($rcv_standby, 'standby_replay', $replay_lsn,
+	'standby_replay');
+
+# 11b. standby_flush exact fencepost.  pg_last_wal_receive_lsn() exposes the
+# flushed walreceiver position even after walreceiver exits, so this probes
+# the standby_flush predicate directly.  standby_write has no stable
+# SQL-visible boundary once walreceiver is stopped; it is covered by the
+# replay-floor and waiter wakeup tests above.
+my $flush_lsn =
+  $rcv_standby->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $flush_covers_replay = $rcv_standby->safe_psql('postgres',
+	"SELECT pg_wal_lsn_diff('$flush_lsn'::pg_lsn, '$replay_lsn'::pg_lsn) >= 0"
+);
+ok($flush_covers_replay eq 't',
+	"standby_flush boundary is not masked by replay floor");
+
+check_wait_for_lsn_fencepost($rcv_standby, 'standby_flush', $flush_lsn,
+	'standby_flush');
+
+# 11c. A sleeping waiter at current + 1 wakes once replay advances
+# past it.  Start the waiter while replay is still paused so it is
+# guaranteed to sleep at replay_lsn_plus regardless of whether
+# flush_lsn > replay_lsn.  Then resume replay and restart the
+# walreceiver to deliver new WAL.
+$rcv_primary->safe_psql('postgres',
+	"INSERT INTO rcv_test VALUES (generate_series(200, 210))");
+
+my $boundary_session = $rcv_standby->background_psql('postgres');
+$boundary_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${replay_lsn_plus}'
+		WITH (MODE 'standby_replay', timeout '30s', no_throw);
+]);
+
+$rcv_standby->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Boundary waiter did not sleep";
+
+$rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+resume_walreceiver($rcv_standby);
+$boundary_session->quit;
+chomp($boundary_session->{stdout});
+is($boundary_session->{stdout},
+	'success',
+	"standby_replay: waiter at current + 1 wakes when replay advances");
+
+$rcv_standby->stop;
+$rcv_primary->stop;
+
+# 12. Timeline switch on a cascade standby.  A WAIT FOR LSN waiter on
+# a cascade standby must survive its upstream's promotion: the
+# cascade walreceiver reconnects on the new timeline and replay
+# continues across the boundary.
+
+my $tl_primary = PostgreSQL::Test::Cluster->new('tl_primary');
+$tl_primary->init(allows_streaming => 1);
+$tl_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$tl_primary->start;
+$tl_primary->safe_psql('postgres',
+	"CREATE TABLE tl_test AS SELECT generate_series(1, 10) AS a");
+
+my $tl_backup = 'tl_backup';
+$tl_primary->backup($tl_backup);
+
+my $tl_standby1 = PostgreSQL::Test::Cluster->new('tl_standby1');
+$tl_standby1->init_from_backup($tl_primary, $tl_backup, has_streaming => 1);
+$tl_standby1->start;
+
+# standby2 cascades from standby1.
+my $tl_backup2 = 'tl_backup2';
+$tl_standby1->backup($tl_backup2);
+
+my $tl_standby2 = PostgreSQL::Test::Cluster->new('tl_standby2');
+$tl_standby2->init_from_backup($tl_standby1, $tl_backup2, has_streaming => 1);
+$tl_standby2->start;
+
+$tl_primary->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(11, 20))");
+$tl_primary->wait_for_catchup($tl_standby1);
+$tl_standby1->wait_for_catchup($tl_standby2);
+
+# Target LSN well past current insert LSN, so reaching it requires
+# WAL produced on the new timeline.  Pause replay on standby2 to
+# guarantee the waiter is asleep when the switch happens.
+my $tl_target = $tl_primary->safe_psql('postgres',
+	"SELECT (pg_current_wal_insert_lsn() + 65536)::text");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$tl_standby2->poll_query_until('postgres',
+	"SELECT pg_get_wal_replay_pause_state() = 'paused'")
+  or die "Timed out waiting for tl_standby2 replay to pause";
+
+my $tl_session = $tl_standby2->background_psql('postgres');
+$tl_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${tl_target}'
+		WITH (MODE 'standby_replay', timeout '60s', no_throw);
+]);
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT count(*) > 0 FROM pg_stat_activity WHERE wait_event = 'WaitForWalReplay'"
+) or die "Cascade waiter did not sleep before promotion";
+
+# Promote standby1 to TLI 2; produce enough WAL on the new timeline
+# to push past tl_target and force a segment switch.
+$tl_standby1->promote;
+$tl_standby1->safe_psql('postgres',
+	"INSERT INTO tl_test VALUES (generate_series(21, 1020))");
+$tl_standby1->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+$tl_standby2->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+$tl_standby2->poll_query_until('postgres',
+	"SELECT received_tli > 1 FROM pg_stat_wal_receiver")
+  or die "tl_standby2 did not follow upstream timeline switch";
+
+$tl_session->quit;
+chomp($tl_session->{stdout});
+is($tl_session->{stdout}, 'success',
+	"WAIT FOR LSN survives upstream promotion and timeline switch on cascade standby"
+);
+
+$tl_standby2->stop;
+$tl_standby1->stop;
+$tl_primary->stop;
+
 done_testing();
-- 
2.51.0



  [application/octet-stream] v8-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch (4.3K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/7-v8-0002-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch)
  download | inline diff:
From d6c2b2c064063ecb80eb5042fa1d7ea0863aa1dc Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:59:13 +0800
Subject: [PATCH v8 2/7] Fix memory ordering in WAIT FOR LSN wakeup mechanism

WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN
position then reads minWaitedLSN; the waiter stores its target into
minWaitedLSN then reads the position.  Without a barrier between each
side's store and load, a CPU may satisfy the load before the store
becomes globally visible, causing either side to miss a concurrent
update.  The result is a missed wakeup: the waiter sleeps indefinitely
until the next unrelated event.

Fix by embedding the required barriers into the atomic operations on
minWaitedLSN:

- In updateMinWaitedLSN(), use pg_atomic_write_membarrier_u64() so the
  waiter's preceding heap update is visible before the new minWaitedLSN
  value is published.

- In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
  fast-path check so the waker's preceding position store is globally
  visible before minWaitedLSN is read.

The waiter side is also covered by the barrier semantics already present
in GetCurrentLSNForWaitType(): GetWalRcvWriteRecPtr() uses an explicit
read barrier (from patch 0001), while the remaining getters acquire a
spinlock, which implies the same ordering.

Also call ResetLatch() unconditionally after WaitLatch(), following the
standard latch loop pattern.  WaitLatch() does not guarantee that all
simultaneously true wake conditions are reported in one return, so a
timeout can race with SetLatch().  If we skip ResetLatch() on a timeout
return, the code performs further asynchronous-state checks before
consuming the latch, violating the latch API's required wait/reset
pattern.  That can leave the latch set across loop exit and cause a
later unrelated WaitLatch() in the same backend to return immediately.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogwait.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 2e31c0d67d7..6a27183c207 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT,
 				 "WaitLSNWaitEvents must match WaitLSNType enum");
 
 /*
- * Get the current LSN for the specified wait type.
+ * Get the current LSN for the specified wait type.  Provide memory
+ * barrier semantics before getting the value.
  */
 XLogRecPtr
 GetCurrentLSNForWaitType(WaitLSNType lsnType)
 {
 	Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT);
 
+	/*
+	 * All of the cases below provide memory barrier semantics:
+	 * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers,
+	 * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks.
+	 */
 	switch (lsnType)
 	{
 		case WAIT_LSN_TYPE_STANDBY_REPLAY:
@@ -184,7 +190,8 @@ updateMinWaitedLSN(WaitLSNType lsnType)
 
 		minWaitedLSN = procInfo->waitLSN;
 	}
-	pg_atomic_write_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
+	/* Pairs with pg_atomic_read_membarrier_u64() in WaitLSNWakeup(). */
+	pg_atomic_write_membarrier_u64(&waitLSNState->minWaitedLSN[i], minWaitedLSN);
 }
 
 /*
@@ -325,10 +332,11 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	/*
 	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
-	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 * "wake all waiters" (e.g., during promotion when recovery ends). Pairs
+	 * with pg_atomic_write_membarrier_u64() in updateMinWaitedLSN().
 	 */
 	if (XLogRecPtrIsValid(currentLSN) &&
-		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+		pg_atomic_read_membarrier_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
@@ -450,8 +458,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ResetLatch(MyLatch);
 	}
 
 	/*
-- 
2.51.0



  [application/octet-stream] v8-0001-Use-barrier-semantics-when-reading-writing-writte.patch (3.0K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/8-v8-0001-Use-barrier-semantics-when-reading-writing-writte.patch)
  download | inline diff:
From 73ec91e1ad442ec052c8f1f964c10c810def6047 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 10:45:52 +0800
Subject: [PATCH v8 1/7] Use barrier semantics when reading/writing writtenUpto

The walreceiver publishes its write position lock-free via writtenUpto.
On weakly-ordered architectures (ARM, PowerPC), both sides of this
handshake need explicit barriers so that the lock-less reader sees a
consistent state.

Use pg_atomic_write_membarrier_u64() at both write sites and
pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr().  This matches
the barrier semantics that GetWalRcvFlushRecPtr() and other LSN-position
functions get implicitly from their spinlock acquire/release, and
protects from bugs caused by expectations of similar barrier guarantees
from different LSN-position functions.

Reported-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz
---
 src/backend/replication/walreceiver.c      |  2 +-
 src/backend/replication/walreceiverfuncs.c | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 8185412a810..c0ac75b54d7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -975,7 +975,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 	}
 
 	/* Update shared-memory status */
-	pg_atomic_write_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
+	pg_atomic_write_membarrier_u64(&WalRcv->writtenUpto, LogstreamResult.Write);
 
 	/*
 	 * If we wrote an LSN that someone was waiting for, notify the waiters.
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index bd5d47be964..9447913c0e7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -321,7 +321,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 		walrcv->flushedUpto = recptr;
 		walrcv->receivedTLI = tli;
 		walrcv->latestChunkStart = recptr;
-		pg_atomic_write_u64(&walrcv->writtenUpto, recptr);
+		/* Pairs with pg_atomic_read_membarrier_u64() in GetWalRcvWriteRecPtr(). */
+		pg_atomic_write_membarrier_u64(&walrcv->writtenUpto, recptr);
 	}
 	walrcv->receiveStart = recptr;
 	walrcv->receiveStartTLI = tli;
@@ -363,14 +364,17 @@ GetWalRcvFlushRecPtr(XLogRecPtr *latestChunkStart, TimeLineID *receiveTLI)
 
 /*
  * Returns the last+1 byte position that walreceiver has written.
- * This returns a recently written value without taking a lock.
+ *
+ * Use pg_atomic_read_membarrier_u64() to ensure that callers see up-to-date
+ * shared memory state, matching the barrier semantics provided by the
+ * spinlock in GetWalRcvFlushRecPtr() and other LSN-position functions.
  */
 XLogRecPtr
 GetWalRcvWriteRecPtr(void)
 {
 	WalRcvData *walrcv = WalRcv;
 
-	return pg_atomic_read_u64(&walrcv->writtenUpto);
+	return pg_atomic_read_membarrier_u64(&walrcv->writtenUpto);
 }
 
 /*
-- 
2.51.0



  [application/octet-stream] v8-0005-Wake-standby_write-standby_flush-waiters-from-the.patch (5.4K, ../../CABPTF7W-gaO=FAkhda=_pDQJjLne68ioNHHU8vuB4iEnswR1=w@mail.gmail.com/9-v8-0005-Wake-standby_write-standby_flush-waiters-from-the.patch)
  download | inline diff:
From ecca80ab5a872e6e52b69584e26f59e7202e1bd6 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v8 5/7] Wake standby_write/standby_flush waiters from the WAL
 replay loop

The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.

Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.

This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.

Reported-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <[email protected]>
---
 src/backend/access/transam/xlogrecovery.c | 10 +++-
 src/test/recovery/t/049_wait_for_lsn.pl   | 64 +++++++++++++++++++++++
 2 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * Wake up processes waiting for standby replay LSN to reach
-			 * current replay position.
+			 * Wake up processes waiting for standby replay, write, or flush
+			 * LSN to reach current replay position.  Replay implies that the
+			 * WAL was already written and flushed to disk, so write and flush
+			 * waiters can be woken at the replay position too.
 			 */
 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
+			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+						  XLogRecoveryCtl->lastReplayedEndRecPtr);
 
 			/* Exit loop if we reached inclusive recovery target */
 			if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 26790fda5be..72dbe06b108 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -744,6 +744,70 @@ $output = $arc_standby->safe_psql(
 ok($output eq "success",
 	"standby_flush succeeds on archive-only standby (getter fallback)");
 
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up.  This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+	"INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+  $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+	"SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+	qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+	't')
+  or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters.  With replay paused, target > replay, so they
+# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_write', timeout '30s', no_throw);
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+	qr/start/, qq[
+	\\echo start
+	WAIT FOR LSN '${arc_target_lsn2}'
+		WITH (MODE 'standby_flush', timeout '30s', no_throw);
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+	"SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay.  The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+chomp($arc_write_session->{stdout});
+chomp($arc_flush_session->{stdout});
+
+is($arc_write_session->{stdout},
+	'success',
+	"standby_write waiter woken by replay on archive-only standby");
+is($arc_flush_session->{stdout},
+	'success',
+	"standby_flush waiter woken by replay on archive-only standby");
+
 $arc_standby->stop;
 $arc_primary->stop;
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-07-06 11:04                                                                                                                                                                                       ` Heikki Linnakangas <[email protected]>
  2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-07 15:25                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 2 replies; 527+ messages in thread

From: Heikki Linnakangas @ 2026-07-06 11:04 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On 01/05/2026 05:44, Xuneng Zhou wrote:
> On Wed, Apr 29, 2026 at 5:01 AM Alexander Korotkov <[email protected] 
> <mailto:[email protected]>> wrote:
> 
>> LGTM, I've added some comments for new functions in 0006.  I propose
>> to push this patchset.  Probably something is still missing and we
>> will have to go back to this.  But it seems to make a lot of aspects
>> much better.
> 
> I reviewed the patchset and found a potential issue in the test for 
> patch 5, similar to the log-checking problem in the cascading timeline- 
> switch test. I've applied a minor fix to address it. Other parts LGTM.
I happened to look around this code now. To recap, the code in the main 
WAL redo loop now looks like this:

> 
> 			/*
> 			 * Apply the record
> 			 */
> 			ApplyWalRecord(xlogreader, record, &replayTLI);
> 
> 			/*
> 			 * Wake up processes waiting for standby replay, write, or flush
> 			 * LSN to reach current replay position.  Replay implies that the
> 			 * WAL was already written and flushed to disk, so write and flush
> 			 * waiters can be woken at the replay position too.
> 			 */
> 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
> 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> 						  XLogRecoveryCtl->lastReplayedEndRecPtr);
> 			WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> 						  XLogRecoveryCtl->lastReplayedEndRecPtr);

That's not wrong, but I've got some comments:

1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or 
atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but 
it feels a little dirty.

2. We're now doing three extra function calls on every WAL record. This 
is a very hot path, and most of the time, we'll just take the fast path 
in WaitLSNWakeup to return without doing anything. Andres and others 
assumed up-thread that it's negligible (we used to have pre-checks here 
in the caller), but I wonder if you did any performance testing?

3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up 
walsenders and walreceivers. They could perhaps use the same wait-lsn 
machinery now, but that's v20 material. However, I think these 
WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so 
that we'd have all the wakeup actions in one place.

4. Once you move those calls inside ApplyWalRecord(), like this:

> @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
>         /*
>          * Update lastReplayedEndRecPtr after this record has been successfully
>          * replayed.
>          */
>         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
>         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
>         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
>         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
>         SpinLockRelease(&XLogRecoveryCtl->info_lck);
>  
> +       /*
> +        * Wake up processes waiting for standby replay, write, or flush LSN to
> +        * reach current replay position.  Replay implies that the WAL was already
> +        * written and flushed to disk, so write and flush waiters can be woken at
> +        * the replay position too.
> +        */
> +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> +
>         /* ------
>          * Wakeup walsenders:
>          *
>          * On the standby, the WAL is flushed first (which will only wake up
>          * physical walsenders) and then applied, which will only wake up logical
>          * walsenders.

It becomes clear that you don't actually need the memory barrier inside 
WaitLSNWakeup(). Not sure if they're needed for other callers, but here 
we have just released a spinlock, which acts as a memory barrier. It 
might not be worth relaxing, but it does seem a little silly.


If nothing else, I'd like to move those calls into ApplyWalRecord() for 
clarity (point 3 above). What do you think?

- Heikki






^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
@ 2026-07-06 13:49                                                                                                                                                                                         ` Xuneng Zhou <[email protected]>
  2026-07-06 14:17                                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-07-06 13:49 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Heikki,

Thanks for looking into this!

On Mon, Jul 6, 2026 at 7:04 PM Heikki Linnakangas <[email protected]> wrote:
                      /*
> >                        * Apply the record
> >                        */
> >                       ApplyWalRecord(xlogreader, record, &replayTLI);
> >
> >                       /*
> >                        * Wake up processes waiting for standby replay, write, or flush
> >                        * LSN to reach current replay position.  Replay implies that the
> >                        * WAL was already written and flushed to disk, so write and flush
> >                        * waiters can be woken at the replay position too.
> >                        */
> >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
>
> That's not wrong, but I've got some comments:
>
> 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or
> atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but
> it feels a little dirty.
>
> 2. We're now doing three extra function calls on every WAL record. This
> is a very hot path, and most of the time, we'll just take the fast path
> in WaitLSNWakeup to return without doing anything. Andres and others
> assumed up-thread that it's negligible (we used to have pre-checks here
> in the caller), but I wonder if you did any performance testing?

Agreed, this is a hot path. The performance impact of these extra
calls doing real work hasn't been measured yet. I'll do some testing.

> 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up
> walsenders and walreceivers. They could perhaps use the same wait-lsn
> machinery now, but that's v20 material. However, I think these
> WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so
> that we'd have all the wakeup actions in one place.

+ 1. This makes the code safer and more readable.

> 4. Once you move those calls inside ApplyWalRecord(), like this:
>
> > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> >         /*
> >          * Update lastReplayedEndRecPtr after this record has been successfully
> >          * replayed.
> >          */
> >         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
> >         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
> >         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
> >         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
> >         SpinLockRelease(&XLogRecoveryCtl->info_lck);
> >
> > +       /*
> > +        * Wake up processes waiting for standby replay, write, or flush LSN to
> > +        * reach current replay position.  Replay implies that the WAL was already
> > +        * written and flushed to disk, so write and flush waiters can be woken at
> > +        * the replay position too.
> > +        */
> > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> > +
> >         /* ------
> >          * Wakeup walsenders:
> >          *
> >          * On the standby, the WAL is flushed first (which will only wake up
> >          * physical walsenders) and then applied, which will only wake up logical
> >          * walsenders.
>
> It becomes clear that you don't actually need the memory barrier inside
> WaitLSNWakeup(). Not sure if they're needed for other callers, but here
> we have just released a spinlock, which acts as a memory barrier. It
> might not be worth relaxing, but it does seem a little silly.

If we made the move here, I think the memory barrier could be relaxed
since other callers are guarded by either the spinlock or full-barrier
atomic write already.  We might also want to make the contract of
WaitLSNWakeup() explicit: callers should not publish the LSN with an
unsynchronized plain store and then immediately probe minWaitedLSN.
Another motivation for doing this might be slightly better performance
though untested.

> If nothing else, I'd like to move those calls into ApplyWalRecord() for
> clarity (point 3 above). What do you think?

Personally + 1.
--
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
  2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-07-06 14:17                                                                                                                                                                                           ` Xuneng Zhou <[email protected]>
  2026-07-08 12:08                                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-07-06 14:17 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Mon, Jul 6, 2026 at 9:49 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi Heikki,
>
> Thanks for looking into this!
>
> On Mon, Jul 6, 2026 at 7:04 PM Heikki Linnakangas <[email protected]> wrote:
>                       /*
> > >                        * Apply the record
> > >                        */
> > >                       ApplyWalRecord(xlogreader, record, &replayTLI);
> > >
> > >                       /*
> > >                        * Wake up processes waiting for standby replay, write, or flush
> > >                        * LSN to reach current replay position.  Replay implies that the
> > >                        * WAL was already written and flushed to disk, so write and flush
> > >                        * waiters can be woken at the replay position too.
> > >                        */
> > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> >
> > That's not wrong, but I've got some comments:
> >
> > 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or
> > atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but
> > it feels a little dirty.
> >
> > 2. We're now doing three extra function calls on every WAL record. This
> > is a very hot path, and most of the time, we'll just take the fast path
> > in WaitLSNWakeup to return without doing anything. Andres and others
> > assumed up-thread that it's negligible (we used to have pre-checks here
> > in the caller), but I wonder if you did any performance testing?
>
> Agreed, this is a hot path. The performance impact of these extra
> calls doing real work hasn't been measured yet. I'll do some testing.
>
> > 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up
> > walsenders and walreceivers. They could perhaps use the same wait-lsn
> > machinery now, but that's v20 material. However, I think these
> > WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so
> > that we'd have all the wakeup actions in one place.
>
> + 1. This makes the code safer and more readable.
>
> > 4. Once you move those calls inside ApplyWalRecord(), like this:
> >
> > > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> > >         /*
> > >          * Update lastReplayedEndRecPtr after this record has been successfully
> > >          * replayed.
> > >          */
> > >         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
> > >         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
> > >         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
> > >         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
> > >         SpinLockRelease(&XLogRecoveryCtl->info_lck);
> > >
> > > +       /*
> > > +        * Wake up processes waiting for standby replay, write, or flush LSN to
> > > +        * reach current replay position.  Replay implies that the WAL was already
> > > +        * written and flushed to disk, so write and flush waiters can be woken at
> > > +        * the replay position too.
> > > +        */
> > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> > > +
> > >         /* ------
> > >          * Wakeup walsenders:
> > >          *
> > >          * On the standby, the WAL is flushed first (which will only wake up
> > >          * physical walsenders) and then applied, which will only wake up logical
> > >          * walsenders.
> >
> > It becomes clear that you don't actually need the memory barrier inside
> > WaitLSNWakeup(). Not sure if they're needed for other callers, but here
> > we have just released a spinlock, which acts as a memory barrier. It
> > might not be worth relaxing, but it does seem a little silly.
>
> If we made the move here, I think the memory barrier could be relaxed
> since other callers are guarded by either the spinlock or full-barrier
> atomic write already.  We might also want to make the contract of

OK, the 'if' here is redundant...

> WaitLSNWakeup() explicit: callers should not publish the LSN with an
> unsynchronized plain store and then immediately probe minWaitedLSN.
> Another motivation for doing this might be slightly better performance
> though untested.
>
> > If nothing else, I'd like to move those calls into ApplyWalRecord() for
> > clarity (point 3 above). What do you think?
>
> Personally + 1.


-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
  2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 14:17                                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-07-08 12:08                                                                                                                                                                                             ` Xuneng Zhou <[email protected]>
  2026-07-08 17:38                                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-07-08 12:08 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Mon, Jul 6, 2026 at 10:17 PM Xuneng Zhou <[email protected]> wrote:
>
> On Mon, Jul 6, 2026 at 9:49 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi Heikki,
> >
> > Thanks for looking into this!
> >
> > On Mon, Jul 6, 2026 at 7:04 PM Heikki Linnakangas <[email protected]> wrote:
> >                       /*
> > > >                        * Apply the record
> > > >                        */
> > > >                       ApplyWalRecord(xlogreader, record, &replayTLI);
> > > >
> > > >                       /*
> > > >                        * Wake up processes waiting for standby replay, write, or flush
> > > >                        * LSN to reach current replay position.  Replay implies that the
> > > >                        * WAL was already written and flushed to disk, so write and flush
> > > >                        * waiters can be woken at the replay position too.
> > > >                        */
> > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > >
> > > That's not wrong, but I've got some comments:
> > >
> > > 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or
> > > atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but
> > > it feels a little dirty.
> > >
> > > 2. We're now doing three extra function calls on every WAL record. This
> > > is a very hot path, and most of the time, we'll just take the fast path
> > > in WaitLSNWakeup to return without doing anything. Andres and others
> > > assumed up-thread that it's negligible (we used to have pre-checks here
> > > in the caller), but I wonder if you did any performance testing?
> >
> > Agreed, this is a hot path. The performance impact of these extra
> > calls doing real work hasn't been measured yet. I'll do some testing.
> >
> > > 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up
> > > walsenders and walreceivers. They could perhaps use the same wait-lsn
> > > machinery now, but that's v20 material. However, I think these
> > > WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so
> > > that we'd have all the wakeup actions in one place.
> >
> > + 1. This makes the code safer and more readable.
> >
> > > 4. Once you move those calls inside ApplyWalRecord(), like this:
> > >
> > > > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> > > >         /*
> > > >          * Update lastReplayedEndRecPtr after this record has been successfully
> > > >          * replayed.
> > > >          */
> > > >         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
> > > >         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
> > > >         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
> > > >         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
> > > >         SpinLockRelease(&XLogRecoveryCtl->info_lck);
> > > >
> > > > +       /*
> > > > +        * Wake up processes waiting for standby replay, write, or flush LSN to
> > > > +        * reach current replay position.  Replay implies that the WAL was already
> > > > +        * written and flushed to disk, so write and flush waiters can be woken at
> > > > +        * the replay position too.
> > > > +        */
> > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> > > > +
> > > >         /* ------
> > > >          * Wakeup walsenders:
> > > >          *
> > > >          * On the standby, the WAL is flushed first (which will only wake up
> > > >          * physical walsenders) and then applied, which will only wake up logical
> > > >          * walsenders.
> > >
> > > It becomes clear that you don't actually need the memory barrier inside
> > > WaitLSNWakeup(). Not sure if they're needed for other callers, but here
> > > we have just released a spinlock, which acts as a memory barrier. It
> > > might not be worth relaxing, but it does seem a little silly.
> >
> > If we made the move here, I think the memory barrier could be relaxed
> > since other callers are guarded by either the spinlock or full-barrier
> > atomic write already.  We might also want to make the contract of
>
> OK, the 'if' here is redundant...

After revisiting the memory barrier in WaitLSNWakeup and why it is
introduced there in a80a593ab63 rather than recalling it from memory,
I think relaxing it here could be unsafe.

In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
fast-path check so the waker's preceding position store is globally
visible before minWaitedLSN is read.

Without the barrier in WaitLSNWakeup(), this interleaving is possible:

Initial:
  minWaitedLSN = PG_UINT64_MAX
  replayLSN = 90

Waiter:
  stores minWaitedLSN = 100
  reads replayLSN before the waker publishes the new replay position
  sees replayLSN = 90
  decides it should sleep

Waker:
  publishes replayLSN = 100
  reads old minWaitedLSN = PG_UINT64_MAX
  skips the wakeup

Then the waiter goes to sleep even though replay has reached its
target LSN. To avoid this, we still need to make sure that the
publication of replayLSN precedes the read of minWaitedLSN, so that
the waker cannot decide "nobody is waiting" before its own progress is
still not visible to the waiter.

--
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
  2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 14:17                                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-08 12:08                                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-07-08 17:38                                                                                                                                                                                               ` Alexander Korotkov <[email protected]>
  2026-07-09 04:53                                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-07-08 17:38 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Jul 8, 2026 at 3:08 PM Xuneng Zhou <[email protected]> wrote:
> On Mon, Jul 6, 2026 at 10:17 PM Xuneng Zhou <[email protected]> wrote:
> >
> > On Mon, Jul 6, 2026 at 9:49 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi Heikki,
> > >
> > > Thanks for looking into this!
> > >
> > > On Mon, Jul 6, 2026 at 7:04 PM Heikki Linnakangas <[email protected]> wrote:
> > >                       /*
> > > > >                        * Apply the record
> > > > >                        */
> > > > >                       ApplyWalRecord(xlogreader, record, &replayTLI);
> > > > >
> > > > >                       /*
> > > > >                        * Wake up processes waiting for standby replay, write, or flush
> > > > >                        * LSN to reach current replay position.  Replay implies that the
> > > > >                        * WAL was already written and flushed to disk, so write and flush
> > > > >                        * waiters can be woken at the replay position too.
> > > > >                        */
> > > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> > > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> > > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> > > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > >
> > > > That's not wrong, but I've got some comments:
> > > >
> > > > 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or
> > > > atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but
> > > > it feels a little dirty.
> > > >
> > > > 2. We're now doing three extra function calls on every WAL record. This
> > > > is a very hot path, and most of the time, we'll just take the fast path
> > > > in WaitLSNWakeup to return without doing anything. Andres and others
> > > > assumed up-thread that it's negligible (we used to have pre-checks here
> > > > in the caller), but I wonder if you did any performance testing?
> > >
> > > Agreed, this is a hot path. The performance impact of these extra
> > > calls doing real work hasn't been measured yet. I'll do some testing.
> > >
> > > > 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up
> > > > walsenders and walreceivers. They could perhaps use the same wait-lsn
> > > > machinery now, but that's v20 material. However, I think these
> > > > WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so
> > > > that we'd have all the wakeup actions in one place.
> > >
> > > + 1. This makes the code safer and more readable.
> > >
> > > > 4. Once you move those calls inside ApplyWalRecord(), like this:
> > > >
> > > > > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> > > > >         /*
> > > > >          * Update lastReplayedEndRecPtr after this record has been successfully
> > > > >          * replayed.
> > > > >          */
> > > > >         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
> > > > >         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
> > > > >         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
> > > > >         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
> > > > >         SpinLockRelease(&XLogRecoveryCtl->info_lck);
> > > > >
> > > > > +       /*
> > > > > +        * Wake up processes waiting for standby replay, write, or flush LSN to
> > > > > +        * reach current replay position.  Replay implies that the WAL was already
> > > > > +        * written and flushed to disk, so write and flush waiters can be woken at
> > > > > +        * the replay position too.
> > > > > +        */
> > > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> > > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> > > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> > > > > +
> > > > >         /* ------
> > > > >          * Wakeup walsenders:
> > > > >          *
> > > > >          * On the standby, the WAL is flushed first (which will only wake up
> > > > >          * physical walsenders) and then applied, which will only wake up logical
> > > > >          * walsenders.
> > > >
> > > > It becomes clear that you don't actually need the memory barrier inside
> > > > WaitLSNWakeup(). Not sure if they're needed for other callers, but here
> > > > we have just released a spinlock, which acts as a memory barrier. It
> > > > might not be worth relaxing, but it does seem a little silly.
> > >
> > > If we made the move here, I think the memory barrier could be relaxed
> > > since other callers are guarded by either the spinlock or full-barrier
> > > atomic write already.  We might also want to make the contract of
> >
> > OK, the 'if' here is redundant...
>
> After revisiting the memory barrier in WaitLSNWakeup and why it is
> introduced there in a80a593ab63 rather than recalling it from memory,
> I think relaxing it here could be unsafe.
>
> In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
> fast-path check so the waker's preceding position store is globally
> visible before minWaitedLSN is read.
>
> Without the barrier in WaitLSNWakeup(), this interleaving is possible:
>
> Initial:
>   minWaitedLSN = PG_UINT64_MAX
>   replayLSN = 90
>
> Waiter:
>   stores minWaitedLSN = 100
>   reads replayLSN before the waker publishes the new replay position
>   sees replayLSN = 90
>   decides it should sleep
>
> Waker:
>   publishes replayLSN = 100
>   reads old minWaitedLSN = PG_UINT64_MAX
>   skips the wakeup
>
> Then the waiter goes to sleep even though replay has reached its
> target LSN. To avoid this, we still need to make sure that the
> publication of replayLSN precedes the read of minWaitedLSN, so that
> the waker cannot decide "nobody is waiting" before its own progress is
> still not visible to the waiter.

Yes, I also think the memory barrier for waker between publishing
replayLSN and reading minWaitedLSN is required.  However, sequence of
three WaitLSNWakeup() calls makes 3 memory barriers while only one is
required.  We could introduce a hierarchy for WAIT_LSN_TYPE_STANDBY_*:
WAIT_LSN_TYPE_STANDBY_FLUSH implies WAIT_LSN_TYPE_STANDBY_WRITE,
WAIT_LSN_TYPE_STANDBY_REPLAY implies WAIT_LSN_TYPE_STANDBY_WRITE and
WAIT_LSN_TYPE_STANDBY_FLUSH.  Then ApplyWalRecord() can call
WaitLSNWakeup() only once and make only 1 memory barrier.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
  2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 14:17                                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-08 12:08                                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-08 17:38                                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-07-09 04:53                                                                                                                                                                                                 ` Xuneng Zhou <[email protected]>
  2026-07-09 12:24                                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-07-09 04:53 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Jul 9, 2026 at 1:38 AM Alexander Korotkov <[email protected]> wrote:
>
> On Wed, Jul 8, 2026 at 3:08 PM Xuneng Zhou <[email protected]> wrote:
> > On Mon, Jul 6, 2026 at 10:17 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > On Mon, Jul 6, 2026 at 9:49 PM Xuneng Zhou <[email protected]> wrote:
> > > >
> > > > Hi Heikki,
> > > >
> > > > Thanks for looking into this!
> > > >
> > > > On Mon, Jul 6, 2026 at 7:04 PM Heikki Linnakangas <[email protected]> wrote:
> > > >                       /*
> > > > > >                        * Apply the record
> > > > > >                        */
> > > > > >                       ApplyWalRecord(xlogreader, record, &replayTLI);
> > > > > >
> > > > > >                       /*
> > > > > >                        * Wake up processes waiting for standby replay, write, or flush
> > > > > >                        * LSN to reach current replay position.  Replay implies that the
> > > > > >                        * WAL was already written and flushed to disk, so write and flush
> > > > > >                        * waiters can be woken at the replay position too.
> > > > > >                        */
> > > > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> > > > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> > > > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > > > >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> > > > > >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> > > > >
> > > > > That's not wrong, but I've got some comments:
> > > > >
> > > > > 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or
> > > > > atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but
> > > > > it feels a little dirty.
> > > > >
> > > > > 2. We're now doing three extra function calls on every WAL record. This
> > > > > is a very hot path, and most of the time, we'll just take the fast path
> > > > > in WaitLSNWakeup to return without doing anything. Andres and others
> > > > > assumed up-thread that it's negligible (we used to have pre-checks here
> > > > > in the caller), but I wonder if you did any performance testing?
> > > >
> > > > Agreed, this is a hot path. The performance impact of these extra
> > > > calls doing real work hasn't been measured yet. I'll do some testing.
> > > >
> > > > > 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up
> > > > > walsenders and walreceivers. They could perhaps use the same wait-lsn
> > > > > machinery now, but that's v20 material. However, I think these
> > > > > WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so
> > > > > that we'd have all the wakeup actions in one place.
> > > >
> > > > + 1. This makes the code safer and more readable.
> > > >
> > > > > 4. Once you move those calls inside ApplyWalRecord(), like this:
> > > > >
> > > > > > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> > > > > >         /*
> > > > > >          * Update lastReplayedEndRecPtr after this record has been successfully
> > > > > >          * replayed.
> > > > > >          */
> > > > > >         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
> > > > > >         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
> > > > > >         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
> > > > > >         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
> > > > > >         SpinLockRelease(&XLogRecoveryCtl->info_lck);
> > > > > >
> > > > > > +       /*
> > > > > > +        * Wake up processes waiting for standby replay, write, or flush LSN to
> > > > > > +        * reach current replay position.  Replay implies that the WAL was already
> > > > > > +        * written and flushed to disk, so write and flush waiters can be woken at
> > > > > > +        * the replay position too.
> > > > > > +        */
> > > > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> > > > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> > > > > > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> > > > > > +
> > > > > >         /* ------
> > > > > >          * Wakeup walsenders:
> > > > > >          *
> > > > > >          * On the standby, the WAL is flushed first (which will only wake up
> > > > > >          * physical walsenders) and then applied, which will only wake up logical
> > > > > >          * walsenders.
> > > > >
> > > > > It becomes clear that you don't actually need the memory barrier inside
> > > > > WaitLSNWakeup(). Not sure if they're needed for other callers, but here
> > > > > we have just released a spinlock, which acts as a memory barrier. It
> > > > > might not be worth relaxing, but it does seem a little silly.
> > > >
> > > > If we made the move here, I think the memory barrier could be relaxed
> > > > since other callers are guarded by either the spinlock or full-barrier
> > > > atomic write already.  We might also want to make the contract of
> > >
> > > OK, the 'if' here is redundant...
> >
> > After revisiting the memory barrier in WaitLSNWakeup and why it is
> > introduced there in a80a593ab63 rather than recalling it from memory,
> > I think relaxing it here could be unsafe.
> >
> > In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the
> > fast-path check so the waker's preceding position store is globally
> > visible before minWaitedLSN is read.
> >
> > Without the barrier in WaitLSNWakeup(), this interleaving is possible:
> >
> > Initial:
> >   minWaitedLSN = PG_UINT64_MAX
> >   replayLSN = 90
> >
> > Waiter:
> >   stores minWaitedLSN = 100
> >   reads replayLSN before the waker publishes the new replay position
> >   sees replayLSN = 90
> >   decides it should sleep
> >
> > Waker:
> >   publishes replayLSN = 100
> >   reads old minWaitedLSN = PG_UINT64_MAX
> >   skips the wakeup
> >
> > Then the waiter goes to sleep even though replay has reached its
> > target LSN. To avoid this, we still need to make sure that the
> > publication of replayLSN precedes the read of minWaitedLSN, so that
> > the waker cannot decide "nobody is waiting" before its own progress is
> > still not visible to the waiter.
>
> Yes, I also think the memory barrier for waker between publishing
> replayLSN and reading minWaitedLSN is required.  However, sequence of
> three WaitLSNWakeup() calls makes 3 memory barriers while only one is
> required.  We could introduce a hierarchy for WAIT_LSN_TYPE_STANDBY_*:
> WAIT_LSN_TYPE_STANDBY_FLUSH implies WAIT_LSN_TYPE_STANDBY_WRITE,
> WAIT_LSN_TYPE_STANDBY_REPLAY implies WAIT_LSN_TYPE_STANDBY_WRITE and
> WAIT_LSN_TYPE_STANDBY_FLUSH.  Then ApplyWalRecord() can call
> WaitLSNWakeup() only once and make only 1 memory barrier.

This optimization seems desirable to me and the hierarchy idea is
clever! I pondered it for a while, it may not be necessary for
WAIT_LSN_TYPE_STANDBY_FLUSH, do we need to add an additional wake-up
call for write waiters at flush lsn publication?
Another less clever idea is to add a special helper for relay wake-up,
we can still use a single memory barrier followed by three plan reads.
However, this seems less unified and needs extra clarification and
safe checking. I am curious about your thoughts on this. Maybe I just
don't get the idea right.

-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
  2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 14:17                                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-08 12:08                                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-08 17:38                                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-07-09 04:53                                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-07-09 12:24                                                                                                                                                                                                   ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-07-09 12:24 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; Noah Misch <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi!

As Noah pointed out in [1], auxiliary processes are supported in the
facility, but proper cleanup is absent when they exit. I have attached
a patch trying to address that as suggested before getting sidetracked
for too long with the issue raised in [2].

[1] https://www.postgresql.org/message-id/[email protected]
[2] https://www.postgresql.org/message-id/flat/20210803023612.iziacxk5syn2r4ut%40alap3.anarazel.de

-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.


Attachments:

  [application/octet-stream] v1-0001-Add-wait-for-lsn-process-exit-cleanup-callback.patch (3.3K, ../../CABPTF7W-opV+1cfF-y0_jmSOtCvo=hzzUM7FCJE8-oGTQyofyQ@mail.gmail.com/2-v1-0001-Add-wait-for-lsn-process-exit-cleanup-callback.patch)
  download | inline diff:
From 638359aa9d6868dc0c7f5919e0596490311e8ee8 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Thu, 9 Jul 2026 20:13:24 +0800
Subject: [PATCH v1] Add wait-for-lsn process-exit cleanup callback

Register an on_shmem_exit callback lazily before a process enters a
wait-for-lsn heap, so stale wait state is removed if the process exits while
waiting.  This keeps cleanup local to xlogwait.c and covers non-backend
callers as well.
---
 src/backend/access/transam/xlogwait.c | 33 +++++++++++++++++++++++++++
 src/backend/storage/lmgr/proc.c       |  6 -----
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 582dde3b061..aa6e549ea63 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -54,6 +54,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
+#include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -69,8 +70,12 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+static bool waitLSNShmemExitRegistered = false;
+
 static void WaitLSNShmemRequest(void *arg);
 static void WaitLSNShmemInit(void *arg);
+static void WaitLSNShmemExit(int code, Datum arg);
+static void RegisterWaitLSNShmemExit(void);
 
 const ShmemCallbacks WaitLSNShmemCallbacks = {
 	.request_fn = WaitLSNShmemRequest,
@@ -378,6 +383,32 @@ WaitLSNCleanup(void)
 	}
 }
 
+/*
+ * Exit callback to clean up any LSN wait state left behind if this process
+ * exits while waiting.  Transaction abort paths call WaitLSNCleanup()
+ * directly.
+ */
+static void
+WaitLSNShmemExit(int code, Datum arg)
+{
+	WaitLSNCleanup();
+}
+
+/*
+ * Register the process-exit cleanup callback before this process can enter a
+ * wait-LSN heap.  One backend can execute WAIT FOR LSN more than once, so
+ * remember whether the callback is already registered.
+ */
+static void
+RegisterWaitLSNShmemExit(void)
+{
+	if (!waitLSNShmemExitRegistered)
+	{
+		on_shmem_exit(WaitLSNShmemExit, 0);
+		waitLSNShmemExitRegistered = true;
+	}
+}
+
 /*
  * Check if the given LSN type requires recovery to be in progress.
  * Standby wait types (replay, write, flush) require recovery;
@@ -412,6 +443,8 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 	/* Should have a valid proc number */
 	Assert(MyProcNumber >= 0 && MyProcNumber < MaxBackends + NUM_AUXILIARY_PROCS);
 
+	RegisterWaitLSNShmemExit();
+
 	if (timeout > 0)
 	{
 		endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), timeout);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 59640bb4f97..2588b14826e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,7 +37,6 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogutils.h"
-#include "access/xlogwait.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -963,11 +962,6 @@ ProcKill(int code, Datum arg)
 	 */
 	LWLockReleaseAll();
 
-	/*
-	 * Cleanup waiting for LSN if any.
-	 */
-	WaitLSNCleanup();
-
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
@ 2026-07-07 15:25                                                                                                                                                                                         ` Alexander Korotkov <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-07-07 15:25 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi, Heikki!

On Mon, Jul 6, 2026 at 2:04 PM Heikki Linnakangas <[email protected]> wrote:
> On 01/05/2026 05:44, Xuneng Zhou wrote:
> > On Wed, Apr 29, 2026 at 5:01 AM Alexander Korotkov <[email protected]
> > <mailto:[email protected]>> wrote:
> >
> >> LGTM, I've added some comments for new functions in 0006.  I propose
> >> to push this patchset.  Probably something is still missing and we
> >> will have to go back to this.  But it seems to make a lot of aspects
> >> much better.
> >
> > I reviewed the patchset and found a potential issue in the test for
> > patch 5, similar to the log-checking problem in the cascading timeline-
> > switch test. I've applied a minor fix to address it. Other parts LGTM.
> I happened to look around this code now. To recap, the code in the main
> WAL redo loop now looks like this:
>
> >
> >                       /*
> >                        * Apply the record
> >                        */
> >                       ApplyWalRecord(xlogreader, record, &replayTLI);
> >
> >                       /*
> >                        * Wake up processes waiting for standby replay, write, or flush
> >                        * LSN to reach current replay position.  Replay implies that the
> >                        * WAL was already written and flushed to disk, so write and flush
> >                        * waiters can be woken at the replay position too.
> >                        */
> >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
> >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
> >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
> >                       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
> >                                                 XLogRecoveryCtl->lastReplayedEndRecPtr);
>
> That's not wrong, but I've got some comments:
>
> 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or
> atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but
> it feels a little dirty.
>
> 2. We're now doing three extra function calls on every WAL record. This
> is a very hot path, and most of the time, we'll just take the fast path
> in WaitLSNWakeup to return without doing anything. Andres and others
> assumed up-thread that it's negligible (we used to have pre-checks here
> in the caller), but I wonder if you did any performance testing?
>
> 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up
> walsenders and walreceivers. They could perhaps use the same wait-lsn
> machinery now, but that's v20 material. However, I think these
> WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so
> that we'd have all the wakeup actions in one place.
>
> 4. Once you move those calls inside ApplyWalRecord(), like this:
>
> > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> >         /*
> >          * Update lastReplayedEndRecPtr after this record has been successfully
> >          * replayed.
> >          */
> >         SpinLockAcquire(&XLogRecoveryCtl->info_lck);
> >         XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
> >         XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
> >         XLogRecoveryCtl->lastReplayedTLI = *replayTLI;
> >         SpinLockRelease(&XLogRecoveryCtl->info_lck);
> >
> > +       /*
> > +        * Wake up processes waiting for standby replay, write, or flush LSN to
> > +        * reach current replay position.  Replay implies that the WAL was already
> > +        * written and flushed to disk, so write and flush waiters can be woken at
> > +        * the replay position too.
> > +        */
> > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr);
> > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr);
> > +       WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr);
> > +
> >         /* ------
> >          * Wakeup walsenders:
> >          *
> >          * On the standby, the WAL is flushed first (which will only wake up
> >          * physical walsenders) and then applied, which will only wake up logical
> >          * walsenders.
>
> It becomes clear that you don't actually need the memory barrier inside
> WaitLSNWakeup(). Not sure if they're needed for other callers, but here
> we have just released a spinlock, which acts as a memory barrier. It
> might not be worth relaxing, but it does seem a little silly.
>
>
> If nothing else, I'd like to move those calls into ApplyWalRecord() for
> clarity (point 3 above). What do you think?

Thank you for bringing this up.

+1 for moving calls into ApplyWalRecord()

Regarding the overhead.  I've tried to apply 158.6 MB of pgbench
generated WAL on my local Macbook M3 Pro.

 * With calls, sec: 1.95/1.86/1.91/1.85/1.90
 * Without calls, sec:  1.83/1.87/1.96/1.90/1.85

So, the difference is less than the measurement error.

Do you prefer to commit the movement of the calls yourself, or do you
prefer me to do it?

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
  2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-04-15 08:30                                                                                                                                                                         ` Xuneng Zhou <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-04-15 08:30 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Thu, Apr 9, 2026 at 11:21 PM Alexander Korotkov <[email protected]> wrote:
>
> On Wed, Apr 8, 2026 at 7:59 AM Xuneng Zhou <[email protected]> wrote:
> > > > Patch 0001 looks OK for me.
> > > > Regarding patch 0002.  Changes made for GetCurrentLSNForWaitType()
> > > > looks reliable for me.  PerformWalRecovery() sets replayed positions
> > > > before starting recovery, and in turn before standby can accept
> > > > connections.  So, changes to WalReceiverMain() don't look necessary to
> > > > me.
> > >
> > > Yeah, GetCurrentLSNForWaitType seems to be the right place to place
> > > the fix. Please see the attached patch 2.
> > >
> > > I also noticed another relevent problem:
> > >
> > > During pure archive recovery (no walreceiver), a backend that issues
> > > 'WAIT FOR LSN ... MODE 'standby_write' with a target ahead of the
> > > current replay position will sleep forever; the startup process
> > > replays past the target but only wakes 'STANDBY_REPLAY' waiters.
> > >
> > > This also affects mixed scenarios: the walreceiver may lag behind
> > > replay (e.g., archive restore has delivered WAL faster than
> > > streaming), so a 'standby_write' waiter could be waiting on WAL that
> > > replay has already consumed.
> > >
> > > I will write a patch to address this soon.
> > >
> >
> > Here is the patch.
>
> I've assembled all the pending patches together.
> 0001 adds memory barrier to GetWalRcvWriteRecPtr() as suggested by
> Andres off-list.
> 0002 is basically [1] by Xuneng, but revised given we have a memory
> barrier in 0001, and my proposal to do ResetLatch() unconditionally
> similar to our other Latch-based loops.
> 0003 and 0004 are [2] by Xuneng.
> 0005 is [3] by Xuneng.
>
> I'm going to add them to Commitfest to run CI over them, and have a
> closer look over them tomorrow.
>
>
> Links.
> 1. https://www.postgresql.org/message-id/CABPTF7Wjk_FbOghyr09Rzu6T2bh-L_KBMqHK%2BzhRXpssU0STyQ%40mail.g...
> 2. https://www.postgresql.org/message-id/CABPTF7X0iV%3DkGC4gjsTj4NvK_NNEJGM3YTc7Obxs5GOiYoMhEw%40mail.g...
> 3. https://www.postgresql.org/message-id/CABPTF7UBdEfyxATWntmCfoJrwB6iPrnhkXO7y_Avmqc2bOn27A%40mail.gma...

I've added the patches to Commitfest.

[1] https://commitfest.postgresql.org/patch/6678/

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-05-19 20:00                                                                                                                                                   ` Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Alexander Lakhin @ 2026-05-19 20:00 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; Xuneng Zhou <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hello Alexander and Xuneng,

06.04.2026 22:49, Alexander Korotkov wrote:
> Thank you, I've pushed your version of patchset.  I made two minor
> corrections for patch #2: mention default mode value in the header
> comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.

I discovered a new test failure, that is apparently caused by new
wait_for_catchup() implementation [1]:
[06:20:23.110](1.069s) not ok 8 - check that the slot state changes to "extended"
[06:20:23.110](0.001s) #   Failed test 'check that the slot state changes to "extended"'
#   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 140.
[06:20:23.111](0.000s) #          got: 'unreserved'
#     expected: 'extended'
[06:20:23.231](0.120s) not ok 9 - check that the slot state changes to "unreserved"
[06:20:23.231](0.000s) #   Failed test 'check that the slot state changes to "unreserved"'
#   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 152.
[06:20:23.231](0.000s) #          got: 'lost|'
#     expected: 'unreserved|t'

I've managed to reproduce such failures with:
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 07eac07b9ce..493ce92674e 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1143,2 +1143,3 @@ XLogWalRcvSendReply(bool force, bool requestReply, bool checkApply)

+pg_usleep(10000);
      /* Get current timestamp. */
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 04aa770d981..19cda3a6b51 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -2521,2 +2521,3 @@ ProcessStandbyReplyMessage(void)

+pg_usleep(100000);
      /* the caller already consumed the msgtype byte */

Concretely, a loop:
for i in {1..100}; do echo "ITERATION $i"; PROVE_TESTS="t/019*" make -s check -C src/test/recovery/ || break; done
failed for me on iterations 2, 1, 7:
ITERATION 7
# +++ tap check in src/test/recovery +++
t/019_replslot_limit.pl .. 8/?
#   Failed test 'check that the slot state changes to "extended"'
#   at t/019_replslot_limit.pl line 140.
#          got: 'unreserved'
#     expected: 'extended'
t/019_replslot_limit.pl .. 26/? # Looks like you failed 1 test of 26.
t/019_replslot_limit.pl .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/26 subtests

With "WAIT FOR LSN" in wait_for_catchup() disabled, 100 iterations
passed.

Having extra logging added, I could see the key difference.
Failed run:
2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 
0/016C0000, targetSeg: 22, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 22
2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM 
pg_replication_slots WHERE slot_name = 'rep1'
vs
Successful run:
2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 
0/01700000, targetSeg: 23, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 23
2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM 
pg_replication_slots WHERE slot_name = 'rep1'

That is, with WAIT FOR LSN, primary in this test may advance
slot->data.restart_lsn to the expected position after wait_for_catchup()
returns.

[1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=goldfish&dt=2026-05-13%2006%3A15%3A03

Best regards,
Alexander

^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
@ 2026-05-20 03:30                                                                                                                                                     ` Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-20 03:30 UTC (permalink / raw)
  To: Alexander Lakhin <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi,

On Tue, May 19, 2026 at 1:00 PM Alexander Lakhin <[email protected]> wrote:
>
> Hello Alexander and Xuneng,
>
> 06.04.2026 22:49, Alexander Korotkov wrote:
>
> Thank you, I've pushed your version of patchset.  I made two minor
> corrections for patch #2: mention default mode value in the header
> comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.
>
>
> I discovered a new test failure, that is apparently caused by new
> wait_for_catchup() implementation [1]:
> [06:20:23.110](1.069s) not ok 8 - check that the slot state changes to "extended"
> [06:20:23.110](0.001s) #   Failed test 'check that the slot state changes to "extended"'
> #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 140.
> [06:20:23.111](0.000s) #          got: 'unreserved'
> #     expected: 'extended'
> [06:20:23.231](0.120s) not ok 9 - check that the slot state changes to "unreserved"
> [06:20:23.231](0.000s) #   Failed test 'check that the slot state changes to "unreserved"'
> #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 152.
> [06:20:23.231](0.000s) #          got: 'lost|'
> #     expected: 'unreserved|t'
>
> I've managed to reproduce such failures with:
> diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
> index 07eac07b9ce..493ce92674e 100644
> --- a/src/backend/replication/walreceiver.c
> +++ b/src/backend/replication/walreceiver.c
> @@ -1143,2 +1143,3 @@ XLogWalRcvSendReply(bool force, bool requestReply, bool checkApply)
>
> +pg_usleep(10000);
>      /* Get current timestamp. */
> diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
> index 04aa770d981..19cda3a6b51 100644
> --- a/src/backend/replication/walsender.c
> +++ b/src/backend/replication/walsender.c
> @@ -2521,2 +2521,3 @@ ProcessStandbyReplyMessage(void)
>
> +pg_usleep(100000);
>      /* the caller already consumed the msgtype byte */
>
> Concretely, a loop:
> for i in {1..100}; do echo "ITERATION $i"; PROVE_TESTS="t/019*" make -s check -C src/test/recovery/ || break; done
> failed for me on iterations 2, 1, 7:
> ITERATION 7
> # +++ tap check in src/test/recovery +++
> t/019_replslot_limit.pl .. 8/?
> #   Failed test 'check that the slot state changes to "extended"'
> #   at t/019_replslot_limit.pl line 140.
> #          got: 'unreserved'
> #     expected: 'extended'
> t/019_replslot_limit.pl .. 26/? # Looks like you failed 1 test of 26.
> t/019_replslot_limit.pl .. Dubious, test returned 1 (wstat 256, 0x100)
> Failed 1/26 subtests
>
> With "WAIT FOR LSN" in wait_for_catchup() disabled, 100 iterations
> passed.
>
> Having extra logging added, I could see the key difference.
> Failed run:
> 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/016C0000, targetSeg: 22, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 22
> 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> vs
> Successful run:
> 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/01700000, targetSeg: 23, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 23
> 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
>
> That is, with WAIT FOR LSN, primary in this test may advance
> slot->data.restart_lsn to the expected position after wait_for_catchup()
> returns.
>
> [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=goldfish&dt=2026-05-13%2006%3A15%3A03

Thanks for reporting this issue.

I think this is related to the semantic change made earlier:
wait_for_catchup() now returns once the standby itself has reached the
target LSN, rather than waiting until the primary observes that
position via pg_stat_replication.

As a result, the primary may not yet have processed the standby
feedback needed to advance the slot's restart_lsn when
wait_for_catchup() returns.

Actually, I was aware of this semantic change and previously thought
it might be harmless. But this failure appears to disprove that. I'll
prepare a patch to fix this shortly.

-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-05-20 05:18                                                                                                                                                       ` Xuneng Zhou <[email protected]>
  2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-20 05:18 UTC (permalink / raw)
  To: Alexander Lakhin <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, May 19, 2026 at 8:30 PM Xuneng Zhou <[email protected]> wrote:
>
> Hi,
>
> On Tue, May 19, 2026 at 1:00 PM Alexander Lakhin <[email protected]> wrote:
> >
> > Hello Alexander and Xuneng,
> >
> > 06.04.2026 22:49, Alexander Korotkov wrote:
> >
> > Thank you, I've pushed your version of patchset.  I made two minor
> > corrections for patch #2: mention default mode value in the header
> > comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.
> >
> >
> > I discovered a new test failure, that is apparently caused by new
> > wait_for_catchup() implementation [1]:
> > [06:20:23.110](1.069s) not ok 8 - check that the slot state changes to "extended"
> > [06:20:23.110](0.001s) #   Failed test 'check that the slot state changes to "extended"'
> > #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 140.
> > [06:20:23.111](0.000s) #          got: 'unreserved'
> > #     expected: 'extended'
> > [06:20:23.231](0.120s) not ok 9 - check that the slot state changes to "unreserved"
> > [06:20:23.231](0.000s) #   Failed test 'check that the slot state changes to "unreserved"'
> > #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 152.
> > [06:20:23.231](0.000s) #          got: 'lost|'
> > #     expected: 'unreserved|t'
> >
> > I've managed to reproduce such failures with:
> > diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
> > index 07eac07b9ce..493ce92674e 100644
> > --- a/src/backend/replication/walreceiver.c
> > +++ b/src/backend/replication/walreceiver.c
> > @@ -1143,2 +1143,3 @@ XLogWalRcvSendReply(bool force, bool requestReply, bool checkApply)
> >
> > +pg_usleep(10000);
> >      /* Get current timestamp. */
> > diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
> > index 04aa770d981..19cda3a6b51 100644
> > --- a/src/backend/replication/walsender.c
> > +++ b/src/backend/replication/walsender.c
> > @@ -2521,2 +2521,3 @@ ProcessStandbyReplyMessage(void)
> >
> > +pg_usleep(100000);
> >      /* the caller already consumed the msgtype byte */
> >
> > Concretely, a loop:
> > for i in {1..100}; do echo "ITERATION $i"; PROVE_TESTS="t/019*" make -s check -C src/test/recovery/ || break; done
> > failed for me on iterations 2, 1, 7:
> > ITERATION 7
> > # +++ tap check in src/test/recovery +++
> > t/019_replslot_limit.pl .. 8/?
> > #   Failed test 'check that the slot state changes to "extended"'
> > #   at t/019_replslot_limit.pl line 140.
> > #          got: 'unreserved'
> > #     expected: 'extended'
> > t/019_replslot_limit.pl .. 26/? # Looks like you failed 1 test of 26.
> > t/019_replslot_limit.pl .. Dubious, test returned 1 (wstat 256, 0x100)
> > Failed 1/26 subtests
> >
> > With "WAIT FOR LSN" in wait_for_catchup() disabled, 100 iterations
> > passed.
> >
> > Having extra logging added, I could see the key difference.
> > Failed run:
> > 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/016C0000, targetSeg: 22, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 22
> > 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> > vs
> > Successful run:
> > 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/01700000, targetSeg: 23, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 23
> > 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> >
> > That is, with WAIT FOR LSN, primary in this test may advance
> > slot->data.restart_lsn to the expected position after wait_for_catchup()
> > returns.
> >
> > [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=goldfish&dt=2026-05-13%2006%3A15%3A03
>
> Thanks for reporting this issue.
>
> I think this is related to the semantic change made earlier:
> wait_for_catchup() now returns once the standby itself has reached the
> target LSN, rather than waiting until the primary observes that
> position via pg_stat_replication.
>
> As a result, the primary may not yet have processed the standby
> feedback needed to advance the slot's restart_lsn when
> wait_for_catchup() returns.
>
> Actually, I was aware of this semantic change and previously thought
> it might be harmless. But this failure appears to disprove that. I'll
> prepare a patch to fix this shortly.

After some consideration, 019_replslot_limit.pl appears to the better
place to place the fix rather than by restoring the old primary-side
polling barrier in wait_for_catchup().

The new wait_for_catchup() behavior is closer to its natural
semantics: for replay/write/flush modes, it waits until the standby
itself has reached the requested LSN. The old implementation used
pg_stat_replication on the primary, which also implied that the
primary had received and processed standby feedback. That was a useful
side effect for this test, but it is not required by most callers.

019_replslot_limit.pl is different because it checks primary-side slot
state. For a physical slot, restart_lsn advances only after the
primary's walsender processes standby feedback. So the test needs an
extra condition beyond ordinary standby catchup.

The patch makes that dependency explicit: wait for the standby to
replay the target LSN, then wait for the slot's restart_lsn on the
primary to pass the same LSN. This keeps wait_for_catchup() focused on
standby catchup while making the slot-specific synchronization visible
in the test that needs it.


-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.


Attachments:

  [application/octet-stream] 0001-Stabilize-replslot-limit-test-after-standby-catchup.patch (3.2K, ../../CABPTF7X61iuyZT3K3rZssi9qyBsfo+cGs4vWBZ6KexHi1L6hMA@mail.gmail.com/2-0001-Stabilize-replslot-limit-test-after-standby-catchup.patch)
  download | inline diff:
From 41eb480a5c52c3ebcee4488e38bef5b8aed59ff5 Mon Sep 17 00:00:00 2001
From: Xuneng Zhou <[email protected]>
Date: Tue, 19 May 2026 22:11:04 -0700
Subject: [PATCH] Stabilize replslot limit test after standby catchup

019_replslot_limit.pl checks primary-side WAL availability for a
physical replication slot after stopping and restarting a standby.
wait_for_catchup() now waits for the standby to reach the target LSN
locally, so it can return before the primary has processed standby
feedback and advanced the slot's restart_lsn.

Make the test wait explicitly for that slot advancement before stopping
the standby.
---
 src/test/recovery/t/019_replslot_limit.pl | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index 7b253e64d9c..0c43acdf8a7 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -12,6 +12,16 @@ use PostgreSQL::Test::Cluster;
 use Test::More;
 use Time::HiRes qw(usleep);
 
+sub wait_for_standby_and_slot_catchup
+{
+	my ($primary, $standby, $slot_name) = @_;
+
+	my $target_lsn = $primary->lsn('write');
+
+	$primary->wait_for_catchup($standby, 'replay', $target_lsn);
+	$primary->wait_for_slot_catchup($slot_name, 'restart', $target_lsn);
+}
+
 # Initialize primary node, setting wal-segsize to 1MB
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1, extra => ['--wal-segsize=1']);
@@ -44,8 +54,9 @@ $node_standby->append_conf('postgresql.conf', "primary_slot_name = 'rep1'");
 
 $node_standby->start;
 
-# Wait until standby has replayed enough data
-$node_primary->wait_for_catchup($node_standby);
+# Wait until the standby has replayed enough data, and the primary has
+# processed feedback advancing the slot's restart_lsn.
+wait_for_standby_and_slot_catchup($node_primary, $node_standby, 'rep1');
 
 # Stop standby
 $node_standby->stop;
@@ -79,7 +90,7 @@ is($result, "reserved|t", 'check that slot is working');
 # The standby can reconnect to primary
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby);
+wait_for_standby_and_slot_catchup($node_primary, $node_standby, 'rep1');
 
 $node_standby->stop;
 
@@ -109,7 +120,7 @@ is($result, "reserved",
 
 # The standby can reconnect to primary
 $node_standby->start;
-$node_primary->wait_for_catchup($node_standby);
+wait_for_standby_and_slot_catchup($node_primary, $node_standby, 'rep1');
 $node_standby->stop;
 
 # wal_keep_size overrides max_slot_wal_keep_size
@@ -128,7 +139,7 @@ $result = $node_primary->safe_psql('postgres',
 
 # The standby can reconnect to primary
 $node_standby->start;
-$node_primary->wait_for_catchup($node_standby);
+wait_for_standby_and_slot_catchup($node_primary, $node_standby, 'rep1');
 $node_standby->stop;
 
 # Advance WAL again without checkpoint, reducing remain by 6 MB.
@@ -155,7 +166,7 @@ is($result, "unreserved|t",
 # The standby still can connect to primary before a checkpoint
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby);
+wait_for_standby_and_slot_catchup($node_primary, $node_standby, 'rep1');
 
 $node_standby->stop;
 
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-05-20 12:16                                                                                                                                                         ` Alexander Korotkov <[email protected]>
  2026-05-23 17:09                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-05-20 12:16 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi, Xuneng!

On Wed, May 20, 2026 at 8:18 AM Xuneng Zhou <[email protected]> wrote:
> On Tue, May 19, 2026 at 8:30 PM Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Tue, May 19, 2026 at 1:00 PM Alexander Lakhin <[email protected]> wrote:
> > >
> > > Hello Alexander and Xuneng,
> > >
> > > 06.04.2026 22:49, Alexander Korotkov wrote:
> > >
> > > Thank you, I've pushed your version of patchset.  I made two minor
> > > corrections for patch #2: mention default mode value in the header
> > > comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.
> > >
> > >
> > > I discovered a new test failure, that is apparently caused by new
> > > wait_for_catchup() implementation [1]:
> > > [06:20:23.110](1.069s) not ok 8 - check that the slot state changes to "extended"
> > > [06:20:23.110](0.001s) #   Failed test 'check that the slot state changes to "extended"'
> > > #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 140.
> > > [06:20:23.111](0.000s) #          got: 'unreserved'
> > > #     expected: 'extended'
> > > [06:20:23.231](0.120s) not ok 9 - check that the slot state changes to "unreserved"
> > > [06:20:23.231](0.000s) #   Failed test 'check that the slot state changes to "unreserved"'
> > > #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 152.
> > > [06:20:23.231](0.000s) #          got: 'lost|'
> > > #     expected: 'unreserved|t'
> > >
> > > I've managed to reproduce such failures with:
> > > diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
> > > index 07eac07b9ce..493ce92674e 100644
> > > --- a/src/backend/replication/walreceiver.c
> > > +++ b/src/backend/replication/walreceiver.c
> > > @@ -1143,2 +1143,3 @@ XLogWalRcvSendReply(bool force, bool requestReply, bool checkApply)
> > >
> > > +pg_usleep(10000);
> > >      /* Get current timestamp. */
> > > diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
> > > index 04aa770d981..19cda3a6b51 100644
> > > --- a/src/backend/replication/walsender.c
> > > +++ b/src/backend/replication/walsender.c
> > > @@ -2521,2 +2521,3 @@ ProcessStandbyReplyMessage(void)
> > >
> > > +pg_usleep(100000);
> > >      /* the caller already consumed the msgtype byte */
> > >
> > > Concretely, a loop:
> > > for i in {1..100}; do echo "ITERATION $i"; PROVE_TESTS="t/019*" make -s check -C src/test/recovery/ || break; done
> > > failed for me on iterations 2, 1, 7:
> > > ITERATION 7
> > > # +++ tap check in src/test/recovery +++
> > > t/019_replslot_limit.pl .. 8/?
> > > #   Failed test 'check that the slot state changes to "extended"'
> > > #   at t/019_replslot_limit.pl line 140.
> > > #          got: 'unreserved'
> > > #     expected: 'extended'
> > > t/019_replslot_limit.pl .. 26/? # Looks like you failed 1 test of 26.
> > > t/019_replslot_limit.pl .. Dubious, test returned 1 (wstat 256, 0x100)
> > > Failed 1/26 subtests
> > >
> > > With "WAIT FOR LSN" in wait_for_catchup() disabled, 100 iterations
> > > passed.
> > >
> > > Having extra logging added, I could see the key difference.
> > > Failed run:
> > > 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/016C0000, targetSeg: 22, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 22
> > > 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> > > vs
> > > Successful run:
> > > 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/01700000, targetSeg: 23, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 23
> > > 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> > >
> > > That is, with WAIT FOR LSN, primary in this test may advance
> > > slot->data.restart_lsn to the expected position after wait_for_catchup()
> > > returns.
> > >
> > > [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=goldfish&dt=2026-05-13%2006%3A15%3A03
> >
> > Thanks for reporting this issue.
> >
> > I think this is related to the semantic change made earlier:
> > wait_for_catchup() now returns once the standby itself has reached the
> > target LSN, rather than waiting until the primary observes that
> > position via pg_stat_replication.
> >
> > As a result, the primary may not yet have processed the standby
> > feedback needed to advance the slot's restart_lsn when
> > wait_for_catchup() returns.
> >
> > Actually, I was aware of this semantic change and previously thought
> > it might be harmless. But this failure appears to disprove that. I'll
> > prepare a patch to fix this shortly.
>
> After some consideration, 019_replslot_limit.pl appears to the better
> place to place the fix rather than by restoring the old primary-side
> polling barrier in wait_for_catchup().
>
> The new wait_for_catchup() behavior is closer to its natural
> semantics: for replay/write/flush modes, it waits until the standby
> itself has reached the requested LSN. The old implementation used
> pg_stat_replication on the primary, which also implied that the
> primary had received and processed standby feedback. That was a useful
> side effect for this test, but it is not required by most callers.
>
> 019_replslot_limit.pl is different because it checks primary-side slot
> state. For a physical slot, restart_lsn advances only after the
> primary's walsender processes standby feedback. So the test needs an
> extra condition beyond ordinary standby catchup.
>
> The patch makes that dependency explicit: wait for the standby to
> replay the target LSN, then wait for the slot's restart_lsn on the
> primary to pass the same LSN. This keeps wait_for_catchup() focused on
> standby catchup while making the slot-specific synchronization visible
> in the test that needs it.

I agree with you.  But do we actually need a
wait_for_standby_and_slot_catchup() wrapper.  I think we can call
$node->wait_for_slot_catchup() directly and simplify the fix.  Check
the attached patch.

------
Regards,
Alexander Korotkov
Supabase


Attachments:

  [application/octet-stream] v2-0001-Stabilize-019_replslot_limit.pl-after-wait_for_ca.patch (3.7K, ../../CAPpHfdtQGQpUXoqWpRghhoG_-PbUtxNTgza=am4-HHa=EmXAVQ@mail.gmail.com/2-v2-0001-Stabilize-019_replslot_limit.pl-after-wait_for_ca.patch)
  download | inline diff:
From 62335545e68ac14335467d3cfe5f4b3fdd2d5d25 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Wed, 20 May 2026 15:12:37 +0300
Subject: [PATCH v2] Stabilize 019_replslot_limit.pl after wait_for_catchup()
 semantic change

wait_for_catchup() now returns as soon as the standby has replayed the
target LSN locally, rather than waiting until the primary observes that
position via pg_stat_replication.  019_replslot_limit.pl, however,
checks primary-side pg_replication_slots state, which depends on the
slot's restart_lsn -- and restart_lsn advances only after the primary's
walsender processes a standby reply.  The previous polling
wait_for_catchup() implicitly waited for that round trip; the
WAIT FOR LSN-based one does not, so the subtests "check that the slot
state changes to 'extended' / 'unreserved'" become flappy (reproducible
with small artificial delays in XLogWalRcvSendReply /
ProcessStandbyReplyMessage).

Replace each wait_for_catchup() in this test with
wait_for_slot_catchup('rep1', 'restart', primary->lsn('write')).
restart_lsn cannot move ahead of the standby's replayed position, so
this single wait transitively covers both the standby replay and the
primary's observation of it, which is exactly the precondition the
slot-state assertions require.

Reported-by: Alexander Lakhin <[email protected]>
Discussion: https://postgr.es/m/[email protected]
Author: Xuneng Zhou <[email protected]>
Author: Alexander Korotkov <[email protected]>
---
 src/test/recovery/t/019_replslot_limit.pl | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index 7b253e64d9c..472aa07587f 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -44,8 +44,12 @@ $node_standby->append_conf('postgresql.conf', "primary_slot_name = 'rep1'");
 
 $node_standby->start;
 
-# Wait until standby has replayed enough data
-$node_primary->wait_for_catchup($node_standby);
+# Wait until the primary has processed standby feedback and advanced
+# the slot's restart_lsn.  restart_lsn moves only after the standby's
+# reply reaches the walsender, so this transitively guarantees that
+# the standby itself has replayed past the target LSN.
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 
 # Stop standby
 $node_standby->stop;
@@ -79,7 +83,8 @@ is($result, "reserved|t", 'check that slot is working');
 # The standby can reconnect to primary
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 
 $node_standby->stop;
 
@@ -109,7 +114,8 @@ is($result, "reserved",
 
 # The standby can reconnect to primary
 $node_standby->start;
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 $node_standby->stop;
 
 # wal_keep_size overrides max_slot_wal_keep_size
@@ -128,7 +134,8 @@ $result = $node_primary->safe_psql('postgres',
 
 # The standby can reconnect to primary
 $node_standby->start;
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 $node_standby->stop;
 
 # Advance WAL again without checkpoint, reducing remain by 6 MB.
@@ -155,7 +162,8 @@ is($result, "unreserved|t",
 # The standby still can connect to primary before a checkpoint
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 
 $node_standby->stop;
 
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-05-23 17:09                                                                                                                                                           ` Xuneng Zhou <[email protected]>
  2026-05-23 18:40                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-23 17:09 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Wed, May 20, 2026 at 5:17 AM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> On Wed, May 20, 2026 at 8:18 AM Xuneng Zhou <[email protected]> wrote:
> > On Tue, May 19, 2026 at 8:30 PM Xuneng Zhou <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Tue, May 19, 2026 at 1:00 PM Alexander Lakhin <[email protected]> wrote:
> > > >
> > > > Hello Alexander and Xuneng,
> > > >
> > > > 06.04.2026 22:49, Alexander Korotkov wrote:
> > > >
> > > > Thank you, I've pushed your version of patchset.  I made two minor
> > > > corrections for patch #2: mention default mode value in the header
> > > > comment, and fallback to polling on has_wal_read_bug sparc64+ext4 bug.
> > > >
> > > >
> > > > I discovered a new test failure, that is apparently caused by new
> > > > wait_for_catchup() implementation [1]:
> > > > [06:20:23.110](1.069s) not ok 8 - check that the slot state changes to "extended"
> > > > [06:20:23.110](0.001s) #   Failed test 'check that the slot state changes to "extended"'
> > > > #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 140.
> > > > [06:20:23.111](0.000s) #          got: 'unreserved'
> > > > #     expected: 'extended'
> > > > [06:20:23.231](0.120s) not ok 9 - check that the slot state changes to "unreserved"
> > > > [06:20:23.231](0.000s) #   Failed test 'check that the slot state changes to "unreserved"'
> > > > #   at /Users/ec2-user/bf/goldfish/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 152.
> > > > [06:20:23.231](0.000s) #          got: 'lost|'
> > > > #     expected: 'unreserved|t'
> > > >
> > > > I've managed to reproduce such failures with:
> > > > diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
> > > > index 07eac07b9ce..493ce92674e 100644
> > > > --- a/src/backend/replication/walreceiver.c
> > > > +++ b/src/backend/replication/walreceiver.c
> > > > @@ -1143,2 +1143,3 @@ XLogWalRcvSendReply(bool force, bool requestReply, bool checkApply)
> > > >
> > > > +pg_usleep(10000);
> > > >      /* Get current timestamp. */
> > > > diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
> > > > index 04aa770d981..19cda3a6b51 100644
> > > > --- a/src/backend/replication/walsender.c
> > > > +++ b/src/backend/replication/walsender.c
> > > > @@ -2521,2 +2521,3 @@ ProcessStandbyReplyMessage(void)
> > > >
> > > > +pg_usleep(100000);
> > > >      /* the caller already consumed the msgtype byte */
> > > >
> > > > Concretely, a loop:
> > > > for i in {1..100}; do echo "ITERATION $i"; PROVE_TESTS="t/019*" make -s check -C src/test/recovery/ || break; done
> > > > failed for me on iterations 2, 1, 7:
> > > > ITERATION 7
> > > > # +++ tap check in src/test/recovery +++
> > > > t/019_replslot_limit.pl .. 8/?
> > > > #   Failed test 'check that the slot state changes to "extended"'
> > > > #   at t/019_replslot_limit.pl line 140.
> > > > #          got: 'unreserved'
> > > > #     expected: 'extended'
> > > > t/019_replslot_limit.pl .. 26/? # Looks like you failed 1 test of 26.
> > > > t/019_replslot_limit.pl .. Dubious, test returned 1 (wstat 256, 0x100)
> > > > Failed 1/26 subtests
> > > >
> > > > With "WAIT FOR LSN" in wait_for_catchup() disabled, 100 iterations
> > > > passed.
> > > >
> > > > Having extra logging added, I could see the key difference.
> > > > Failed run:
> > > > 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/016C0000, targetSeg: 22, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 22
> > > > 2026-05-19 22:01:37.968 EEST client backend[3632148] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> > > > vs
> > > > Successful run:
> > > > 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl LOG:  !!!GetWALAvailability| targetLSN: 0/01700000, targetSeg: 23, oldestSlotSeg: 23, oldestSegMaxWalSize: 24, oldestSeg: 23
> > > > 2026-05-19 22:04:18.102 EEST client backend[3633761] 019_replslot_limit.pl STATEMENT:  SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'
> > > >
> > > > That is, with WAIT FOR LSN, primary in this test may advance
> > > > slot->data.restart_lsn to the expected position after wait_for_catchup()
> > > > returns.
> > > >
> > > > [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=goldfish&dt=2026-05-13%2006%3A15%3A03
> > >
> > > Thanks for reporting this issue.
> > >
> > > I think this is related to the semantic change made earlier:
> > > wait_for_catchup() now returns once the standby itself has reached the
> > > target LSN, rather than waiting until the primary observes that
> > > position via pg_stat_replication.
> > >
> > > As a result, the primary may not yet have processed the standby
> > > feedback needed to advance the slot's restart_lsn when
> > > wait_for_catchup() returns.
> > >
> > > Actually, I was aware of this semantic change and previously thought
> > > it might be harmless. But this failure appears to disprove that. I'll
> > > prepare a patch to fix this shortly.
> >
> > After some consideration, 019_replslot_limit.pl appears to the better
> > place to place the fix rather than by restoring the old primary-side
> > polling barrier in wait_for_catchup().
> >
> > The new wait_for_catchup() behavior is closer to its natural
> > semantics: for replay/write/flush modes, it waits until the standby
> > itself has reached the requested LSN. The old implementation used
> > pg_stat_replication on the primary, which also implied that the
> > primary had received and processed standby feedback. That was a useful
> > side effect for this test, but it is not required by most callers.
> >
> > 019_replslot_limit.pl is different because it checks primary-side slot
> > state. For a physical slot, restart_lsn advances only after the
> > primary's walsender processes standby feedback. So the test needs an
> > extra condition beyond ordinary standby catchup.
> >
> > The patch makes that dependency explicit: wait for the standby to
> > replay the target LSN, then wait for the slot's restart_lsn on the
> > primary to pass the same LSN. This keeps wait_for_catchup() focused on
> > standby catchup while making the slot-specific synchronization visible
> > in the test that needs it.
>
> I agree with you.  But do we actually need a
> wait_for_standby_and_slot_catchup() wrapper.  I think we can call
> $node->wait_for_slot_catchup() directly and simplify the fix.  Check
> the attached patch.
>

The patch looks good to me. I agree that the wait_for_slot_catchup is
not needed and could be misleading. This change would make the exact
synchronization point and its intention clearer. The only price we
need to pay here is bringing back the polling. But it seems acceptable
since the cost was there in the pre-wait-for-lsn era. And thanks for
writing the great commit message!

-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-23 17:09                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-05-23 18:40                                                                                                                                                             ` Xuneng Zhou <[email protected]>
  2026-05-25 09:00                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-23 18:40 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

>
>
> > I agree with you.  But do we actually need a
> > wait_for_standby_and_slot_catchup() wrapper.  I think we can call
> > $node->wait_for_slot_catchup() directly and simplify the fix.  Check
> > the attached patch.
> >
>
> The patch looks good to me. I agree that the wait_for_slot_catchup is
> not needed and could be misleading. This change would make the exact
> synchronization point and its intention clearer. The only price we
> need to pay here is bringing back the polling. But it seems acceptable
> since the cost was there in the pre-wait-for-lsn era. And thanks for
> writing the great commit message!
>

Sorry for copy-pasting the wrong function name. It should be
wait_for_catchup().

>

Regards,
Xuneng Zhou
HighGo Software Co., Ltd.


^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-23 17:09                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-23 18:40                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-05-25 09:00                                                                                                                                                               ` Alexander Korotkov <[email protected]>
  2026-05-26 01:48                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2026-05-25 09:00 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Sat, May 23, 2026 at 9:40 PM Xuneng Zhou <[email protected]> wrote:
>> > I agree with you.  But do we actually need a
>> > wait_for_standby_and_slot_catchup() wrapper.  I think we can call
>> > $node->wait_for_slot_catchup() directly and simplify the fix.  Check
>> > the attached patch.
>> >
>>
>> The patch looks good to me. I agree that the wait_for_slot_catchup is
>> not needed and could be misleading. This change would make the exact
>> synchronization point and its intention clearer. The only price we
>> need to pay here is bringing back the polling. But it seems acceptable
>> since the cost was there in the pre-wait-for-lsn era. And thanks for
>> writing the great commit message!
>
>
> Sorry for copy-pasting the wrong function name. It should be wait_for_catchup().

Good, thank you.  I'll push it if no objections.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-23 17:09                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-23 18:40                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-25 09:00                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2026-05-26 01:48                                                                                                                                                                 ` Xuneng Zhou <[email protected]>
  2026-05-26 15:53                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-26 01:48 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Mon, May 25, 2026 at 5:00 PM Alexander Korotkov <[email protected]> wrote:
>
> On Sat, May 23, 2026 at 9:40 PM Xuneng Zhou <[email protected]> wrote:
> >> > I agree with you.  But do we actually need a
> >> > wait_for_standby_and_slot_catchup() wrapper.  I think we can call
> >> > $node->wait_for_slot_catchup() directly and simplify the fix.  Check
> >> > the attached patch.
> >> >
> >>
> >> The patch looks good to me. I agree that the wait_for_slot_catchup is
> >> not needed and could be misleading. This change would make the exact
> >> synchronization point and its intention clearer. The only price we
> >> need to pay here is bringing back the polling. But it seems acceptable
> >> since the cost was there in the pre-wait-for-lsn era. And thanks for
> >> writing the great commit message!
> >
> >
> > Sorry for copy-pasting the wrong function name. It should be wait_for_catchup().
>
> Good, thank you.  I'll push it if no objections.

While reading 019_replslot_limit.pl, Codex pointed out a few
inconsistencies in the comments. I verified them and they look real.
Would you mind doing a small cleanup as well?

-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.


Attachments:

  [application/octet-stream] v2-0002-Clean-up-replslot-limit-test-comments.patch (2.6K, ../../CABPTF7XxDonXAcz6DsN6AUJB3swYrZkJHq3UCDaD3Q2H+j0gUA@mail.gmail.com/2-v2-0002-Clean-up-replslot-limit-test-comments.patch)
  download | inline diff:
From 4f311e78e316423de9114aff14d859ba7c6aa76e Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 26 May 2026 09:40:04 +0800
Subject: [PATCH v2 2/2] Clean up replslot limit test comments

Update stale comments and test names in 019_replslot_limit.pl to match
the actual WAL advancement and wal_status checks. Also remove a redundant
standby stop in the inactive_since coverage.
---
 src/test/recovery/t/019_replslot_limit.pl | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index 472aa07587f..0984f999e13 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -60,7 +60,7 @@ $result = $node_primary->safe_psql('postgres',
 );
 is($result, "reserved|t", 'check the catching-up state');
 
-# Advance WAL by five segments (= 5MB) on primary
+# Advance WAL by one segment (= 1MB) on primary
 $node_primary->advance_wal(1);
 $node_primary->safe_psql('postgres', "CHECKPOINT;");
 
@@ -110,7 +110,7 @@ $node_primary->safe_psql('postgres', "CHECKPOINT;");
 $result = $node_primary->safe_psql('postgres',
 	"SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
 is($result, "reserved",
-	'check that safe_wal_size gets close to the current LSN');
+	'check that slot remains reserved after advancing WAL');
 
 # The standby can reconnect to primary
 $node_standby->start;
@@ -121,7 +121,7 @@ $node_standby->stop;
 # wal_keep_size overrides max_slot_wal_keep_size
 $result = $node_primary->safe_psql('postgres',
 	"ALTER SYSTEM SET wal_keep_size to '8MB'; SELECT pg_reload_conf();");
-# Advance WAL again then checkpoint, reducing remain by 6 MB.
+# Advance WAL again, reducing remain by 6 MB.
 $node_primary->advance_wal(6);
 $result = $node_primary->safe_psql('postgres',
 	"SELECT wal_status as remain FROM pg_replication_slots WHERE slot_name = 'rep1'"
@@ -141,7 +141,7 @@ $node_standby->stop;
 # Advance WAL again without checkpoint, reducing remain by 6 MB.
 $node_primary->advance_wal(6);
 
-# Slot gets into 'reserved' state
+# Slot gets into 'extended' state
 $result = $node_primary->safe_psql('postgres',
 	"SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
 is($result, "extended", 'check that the slot state changes to "extended"');
@@ -480,8 +480,6 @@ is( $primary4->safe_psql(
 	't',
 	'last inactive time for an inactive physical slot is updated correctly');
 
-$standby4->stop;
-
 # Testcase end: Check inactive_since property of the streaming standby's slot
 # =============================================================================
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
  2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-23 17:09                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-23 18:40                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-05-25 09:00                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-05-26 01:48                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2026-05-26 15:53                                                                                                                                                                   ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2026-05-26 15:53 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Heikki Linnakangas <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Tue, May 26, 2026 at 9:48 AM Xuneng Zhou <[email protected]> wrote:
>
> On Mon, May 25, 2026 at 5:00 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Sat, May 23, 2026 at 9:40 PM Xuneng Zhou <[email protected]> wrote:
> > >> > I agree with you.  But do we actually need a
> > >> > wait_for_standby_and_slot_catchup() wrapper.  I think we can call
> > >> > $node->wait_for_slot_catchup() directly and simplify the fix.  Check
> > >> > the attached patch.
> > >> >
> > >>
> > >> The patch looks good to me. I agree that the wait_for_slot_catchup is
> > >> not needed and could be misleading. This change would make the exact
> > >> synchronization point and its intention clearer. The only price we
> > >> need to pay here is bringing back the polling. But it seems acceptable
> > >> since the cost was there in the pre-wait-for-lsn era. And thanks for
> > >> writing the great commit message!
> > >
> > >
> > > Sorry for copy-pasting the wrong function name. It should be wait_for_catchup().
> >
> > Good, thank you.  I'll push it if no objections.
>
> While reading 019_replslot_limit.pl, Codex pointed out a few
> inconsistencies in the comments. I verified them and they look real.
> Would you mind doing a small cleanup as well?

I updated the comment above wait_for_slot_catchup to reflect its usage.

-- 
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.


Attachments:

  [application/octet-stream] v3-0001-Stabilize-019_replslot_limit.pl-after-wait_for_ca.patch (3.7K, ../../CABPTF7Xp0fUK0LiZVeGSNse3Wb6jRy_c9u2TOOBhmgx3K3KACA@mail.gmail.com/2-v3-0001-Stabilize-019_replslot_limit.pl-after-wait_for_ca.patch)
  download | inline diff:
From cbbe124cd5a397764721f48e27de4020f8b46603 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 26 May 2026 09:37:24 +0800
Subject: [PATCH v3 1/2] Stabilize 019_replslot_limit.pl after
 wait_for_catchup() semantic change

wait_for_catchup() now returns as soon as the standby has replayed the
target LSN locally, rather than waiting until the primary observes that
position via pg_stat_replication. 019_replslot_limit.pl, however,
checks primary-side pg_replication_slots state, which depends on the
slot's restart_lsn -- and restart_lsn advances only after the primary's
walsender processes a standby reply. The previous polling
wait_for_catchup() implicitly waited for that round trip; the
WAIT FOR LSN-based one does not, so the subtests "check that the slot
state changes to 'extended' / 'unreserved'" become flappy (reproducible
with small artificial delays in XLogWalRcvSendReply /
ProcessStandbyReplyMessage).

Replace each wait_for_catchup() in this test with
wait_for_slot_catchup('rep1', 'restart', primary->lsn('write')).
restart_lsn cannot move ahead of the standby's replayed position, so
this single wait transitively covers both the standby replay and the
primary's observation of it, which is exactly the precondition the
slot-state assertions require.

Reported-by: Alexander Lakhin <[email protected]>
Discussion: https://postgr.es/m/[email protected]
Author: Xuneng Zhou <[email protected]>
Author: Alexander Korotkov <[email protected]>
---
 src/test/recovery/t/019_replslot_limit.pl | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index 7b253e64d9c..882ffb66550 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -44,8 +44,12 @@ $node_standby->append_conf('postgresql.conf', "primary_slot_name = 'rep1'");
 
 $node_standby->start;
 
-# Wait until standby has replayed enough data
-$node_primary->wait_for_catchup($node_standby);
+# Wait until the primary has processed standby feedback and advanced the
+# slot's restart_lsn.  For a physical slot, restart_lsn is updated from
+# the standby's reported flush position, so this waits for the primary-side
+# slot state that the following wal_status checks depend on.
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 
 # Stop standby
 $node_standby->stop;
@@ -79,7 +83,8 @@ is($result, "reserved|t", 'check that slot is working');
 # The standby can reconnect to primary
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 
 $node_standby->stop;
 
@@ -109,7 +114,8 @@ is($result, "reserved",
 
 # The standby can reconnect to primary
 $node_standby->start;
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 $node_standby->stop;
 
 # wal_keep_size overrides max_slot_wal_keep_size
@@ -128,7 +134,8 @@ $result = $node_primary->safe_psql('postgres',
 
 # The standby can reconnect to primary
 $node_standby->start;
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 $node_standby->stop;
 
 # Advance WAL again without checkpoint, reducing remain by 6 MB.
@@ -155,7 +162,8 @@ is($result, "unreserved|t",
 # The standby still can connect to primary before a checkpoint
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby);
+$node_primary->wait_for_slot_catchup('rep1', 'restart',
+	$node_primary->lsn('write'));
 
 $node_standby->stop;
 
-- 
2.51.0



  [application/octet-stream] v3-0002-Clean-up-replslot-limit-test-comments.patch (2.6K, ../../CABPTF7Xp0fUK0LiZVeGSNse3Wb6jRy_c9u2TOOBhmgx3K3KACA@mail.gmail.com/3-v3-0002-Clean-up-replslot-limit-test-comments.patch)
  download | inline diff:
From a50f27e72ba8b0f99ad67d2f66c1db5ddd55deb8 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Tue, 26 May 2026 09:40:04 +0800
Subject: [PATCH v3 2/2] Clean up replslot limit test comments

Update stale comments and test names in 019_replslot_limit.pl to match
the actual WAL advancement and wal_status checks. Remove a redundant
standby stop in the inactive_since coverage.
---
 src/test/recovery/t/019_replslot_limit.pl | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index 882ffb66550..a412faf51c6 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -60,7 +60,7 @@ $result = $node_primary->safe_psql('postgres',
 );
 is($result, "reserved|t", 'check the catching-up state');
 
-# Advance WAL by five segments (= 5MB) on primary
+# Advance WAL by one segment (= 1MB) on primary
 $node_primary->advance_wal(1);
 $node_primary->safe_psql('postgres', "CHECKPOINT;");
 
@@ -110,7 +110,7 @@ $node_primary->safe_psql('postgres', "CHECKPOINT;");
 $result = $node_primary->safe_psql('postgres',
 	"SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
 is($result, "reserved",
-	'check that safe_wal_size gets close to the current LSN');
+	'check that slot remains reserved after advancing WAL');
 
 # The standby can reconnect to primary
 $node_standby->start;
@@ -121,7 +121,7 @@ $node_standby->stop;
 # wal_keep_size overrides max_slot_wal_keep_size
 $result = $node_primary->safe_psql('postgres',
 	"ALTER SYSTEM SET wal_keep_size to '8MB'; SELECT pg_reload_conf();");
-# Advance WAL again then checkpoint, reducing remain by 6 MB.
+# Advance WAL again, reducing remain by 6 MB.
 $node_primary->advance_wal(6);
 $result = $node_primary->safe_psql('postgres',
 	"SELECT wal_status as remain FROM pg_replication_slots WHERE slot_name = 'rep1'"
@@ -141,7 +141,7 @@ $node_standby->stop;
 # Advance WAL again without checkpoint, reducing remain by 6 MB.
 $node_primary->advance_wal(6);
 
-# Slot gets into 'reserved' state
+# Slot gets into 'extended' state
 $result = $node_primary->safe_psql('postgres',
 	"SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
 is($result, "extended", 'check that the slot state changes to "extended"');
@@ -480,8 +480,6 @@ is( $primary4->safe_psql(
 	't',
 	'last inactive time for an inactive physical slot is updated correctly');
 
-$standby4->stop;
-
 # Testcase end: Check inactive_since property of the streaming standby's slot
 # =============================================================================
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
  2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
  2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
@ 2026-01-07 08:06                                                                                                                     ` Alexander Korotkov <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Alexander Korotkov @ 2026-01-07 08:06 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Xuneng Zhou <[email protected]>; Álvaro Herrera <[email protected]>; Chao Li <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>

On Wed, Jan 7, 2026, 02:32 Andres Freund <[email protected]> wrote:

> Hi,
>
> On 2026-01-06 18:42:59 +1300, Thomas Munro wrote:
> > Could this be causing the recent flapping failures on CI/macOS in
> > recovery/031_recovery_conflict?  I didn't have time to dig personally
> > but f30848cb looks relevant:
> >
> > Waiting for replication conn standby's replay_lsn to pass 0/03467F58 on
> primary
> > error running SQL: 'psql:<stdin>:1: ERROR:  canceling statement due to
> > conflict with recovery
> > DETAIL:  User was or might have been using tablespace that must be
> dropped.'
> > while running 'psql --no-psqlrc --no-align --tuples-only --quiet
> > --dbname port=25195
> > host=/var/folders/g9/7rkt8rt1241bwwhd3_s8ndp40000gn/T/LqcCJnsueI
> > dbname='postgres' --file - --variable ON_ERROR_STOP=1' with sql 'WAIT
> > FOR LSN '0/03467F58' WITH (MODE 'standby_replay', timeout '180s',
> > no_throw);' at
> /Users/admin/pgsql/src/test/perl/PostgreSQL/Test/Cluster.pm
> > line 2300.
> >
> > https://cirrus-ci.com/task/5771274900733952
> >
> > The master branch in time-descending order, macOS tasks only:
> >
> >      task_id      | substring |  status
> > ------------------+-----------+-----------
> >  6460882231754752 | c970bdc0  | FAILED
> >  5771274900733952 | 6ca8506e  | FAILED
> >  6217757068361728 | 63ed3bc7  | FAILED
> >  5980650261446656 | ae283736  | FAILED
> >  6585898394976256 | 5f13999a  | COMPLETED
> >  4527474786172928 | 7f9acc9b  | COMPLETED
> >  4826100842364928 | e8d4e94a  | COMPLETED
> >  4540563027918848 | b9ee5f2d  | FAILED
> >  6358528648019968 | c5af141c  | FAILED
> >  5998005284765696 | e212a0f8  | COMPLETED
> >  6488580526178304 | b85d5dc0  | FAILED
> >  5034091344560128 | 7dc95cc3  | ABORTED
> >  5688692477526016 | bb048e31  | COMPLETED
> >  5481187977723904 | d351063e  | COMPLETED
> >  5101831568752640 | f30848cb  | COMPLETED <-- the change
> >  6395317408497664 | 3f33b63d  | COMPLETED
> >  6741325208354816 | 877ae5db  | COMPLETED
> >  4594007789010944 | de746e0d  | COMPLETED
> >  6497208998035456 | 461b8cc9  | COMPLETED
>
> The failure rates of this are very high - the majority of the CI runs on
> the
> postgres/postgres repos failed since the change went in. Which then also
> means
> cfbot has a very high spurious failure rate. I think we need to revert this
> change until the problem has been verified as fixed.
>

This is fair. I will revert the commit causing the failures in the next few
hours.

------
Regards,
Alexander Korotkov

>


^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-13 20:32                                                                 ` Tomas Vondra <[email protected]>
  2025-11-14 01:49                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2 siblings, 1 reply; 527+ messages in thread

From: Tomas Vondra @ 2025-11-13 20:32 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Xuneng Zhou <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Yura Sokolov <[email protected]>

On 11/5/25 10:51, Alexander Korotkov wrote:
> Hi!
> 
> On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
>> On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
>>> On 2025-Nov-03, Alexander Korotkov wrote:
>>>
>>>> I'd like to give this subject another chance for pg19.  I'm going to
>>>> push this if no objections.
>>>
>>> Sure.  I don't understand why patches 0002 and 0003 are separate though.
>>
>> FWIW, I appreciate such splits. Even if the functionality isn't usable
>> independently, it's still different type of code that's affected. And the
>> patches are each big enough to make that worthwhile for easier review.
> 
> Thank you for the feedback, pushed.
> 

Hi,

The new TAP test 049_wait_for_lsn.pl introduced by this commit, because
it takes a long time - about 65 seconds on my laptop. That's about 25%
of the whole src/test/recovery, more than any other test.

And most of the time there's nothing happening - these are the two log
messages showing the 60-second wait:

2025-11-13 21:12:39.949 CET checkpointer[562597] LOG:  checkpoint
complete: wrote 9 buffers (7.0%), wrote 3 SLRU buffers; 0 WAL file(s)
added, 0 removed, 2 recycled; write=0.906 s, sync=0.001 s, total=0.907
s; sync files=0, longest=0.000 s, average=0.000 s; distance=32768 kB,
estimate=32768 kB; lsn=0/040000B8, redo lsn=0/04000060

2025-11-13 21:13:38.994 CET client backend[562727] 049_wait_for_lsn.pl
ERROR:  recovery is not in progress

So there's a checkpoint, 60 seconds of nothing, and then a failure. I
haven't looked into why it waits for 1 minute exactly, but adding 60
seconds to check-world is somewhat annoying.

While at it, I noticed a couple comments refer to WaitForLSNReplay, but
but I think that got renamed simply to WaitForLSN.


regards

-- 
Tomas Vondra





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-13 20:32                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
@ 2025-11-14 01:49                                                                   ` Xuneng Zhou <[email protected]>
  2025-11-15 10:29                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-14 01:49 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Yura Sokolov <[email protected]>

Hi Tomas,

On Fri, Nov 14, 2025 at 4:32 AM Tomas Vondra <[email protected]> wrote:
>
> On 11/5/25 10:51, Alexander Korotkov wrote:
> > Hi!
> >
> > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> >> On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> >>> On 2025-Nov-03, Alexander Korotkov wrote:
> >>>
> >>>> I'd like to give this subject another chance for pg19.  I'm going to
> >>>> push this if no objections.
> >>>
> >>> Sure.  I don't understand why patches 0002 and 0003 are separate though.
> >>
> >> FWIW, I appreciate such splits. Even if the functionality isn't usable
> >> independently, it's still different type of code that's affected. And the
> >> patches are each big enough to make that worthwhile for easier review.
> >
> > Thank you for the feedback, pushed.
> >
>
> Hi,
>
> The new TAP test 049_wait_for_lsn.pl introduced by this commit, because
> it takes a long time - about 65 seconds on my laptop. That's about 25%
> of the whole src/test/recovery, more than any other test.
>
> And most of the time there's nothing happening - these are the two log
> messages showing the 60-second wait:
>
> 2025-11-13 21:12:39.949 CET checkpointer[562597] LOG:  checkpoint
> complete: wrote 9 buffers (7.0%), wrote 3 SLRU buffers; 0 WAL file(s)
> added, 0 removed, 2 recycled; write=0.906 s, sync=0.001 s, total=0.907
> s; sync files=0, longest=0.000 s, average=0.000 s; distance=32768 kB,
> estimate=32768 kB; lsn=0/040000B8, redo lsn=0/04000060
>
> 2025-11-13 21:13:38.994 CET client backend[562727] 049_wait_for_lsn.pl
> ERROR:  recovery is not in progress
>
> So there's a checkpoint, 60 seconds of nothing, and then a failure. I
> haven't looked into why it waits for 1 minute exactly, but adding 60
> seconds to check-world is somewhat annoying.

Thanks for looking into this!

I did a quick analysis for this prolonged waiting:

In WaitLSNWakeup() (xlogwait.c:267), the fast-path check incorrectly
handled InvalidXLogRecPtr:
/* Fast path check */
if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
    return;  // Issue: Returns early when currentLSN = 0

When currentLSN = InvalidXLogRecPtr (0), meaning "wake all waiters",
the check compared:
- minWaitedLSN (e.g., 0x570CC048) > 0 → TRUE
- Result: function returned early without waking anyone

When It Happened
During standby promotion, xlog.c:6246 calls:

WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);

This should wake all LSN waiters, but the bug prevented it. WAIT FOR
LSN commands could wait indefinitely. Test 049_wait_for_lsn.pl took 68
seconds instead of ~9 seconds.

if the above analysis is sound, the fix could be like:

Proposed fix:
Added a validity check before the comparison:
/*
 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
 * "wake all waiters" (e.g., during promotion when recovery ends).
 */
if (XLogRecPtrIsValid(currentLSN) &&
    pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
    return;

Result:
Test time: 68s → 9s
WAIT FOR LSN exits immediately on promotion (62ms vs 60s)

> While at it, I noticed a couple comments refer to WaitForLSNReplay, but
> but I think that got renamed simply to WaitForLSN.

Please check the attached patch for replacing them.

-- 
Best,
Xuneng


Attachments:

  [application/octet-stream] v1-0001-Fix-incorrect-function-name-in-comments.patch (1.3K, ../../CABPTF7UieOYbOgH3EnQCasaqcT1T4N6V2wammwrWCohQTnD_Lw@mail.gmail.com/2-v1-0001-Fix-incorrect-function-name-in-comments.patch)
  download | inline diff:
From ce6227035eab97b6a67d97fd58e88dc1392a47c7 Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 14 Nov 2025 09:39:31 +0800
Subject: [PATCH v1] Fix incorrect function name in comments

Update comments to reference WaitForLSN() instead of the outdated
WaitForLSNReplay() function name.
---
 src/backend/commands/wait.c   | 2 +-
 src/include/access/xlogwait.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c
index 67068a92dbf..9c4764cf896 100644
--- a/src/backend/commands/wait.c
+++ b/src/backend/commands/wait.c
@@ -143,7 +143,7 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest)
 	waitLSNResult = WaitForLSN(WAIT_LSN_TYPE_REPLAY, lsn, timeout);
 
 	/*
-	 * Process the result of WaitForLSNReplay().  Throw appropriate error if
+	 * Process the result of WaitForLSN().  Throw appropriate error if
 	 * needed.
 	 */
 	switch (waitLSNResult)
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index 4dc328b1b07..f43e481c3b9 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -20,7 +20,7 @@
 #include "tcop/dest.h"
 
 /*
- * Result statuses for WaitForLSNReplay().
+ * Result statuses for WaitForLSN().
  */
 typedef enum
 {
-- 
2.51.0



  [application/octet-stream] v1-0001-Fix-WaitLSNWakeup-fast-path-check-for-InvalidXLog.patch (1.5K, ../../CABPTF7UieOYbOgH3EnQCasaqcT1T4N6V2wammwrWCohQTnD_Lw@mail.gmail.com/3-v1-0001-Fix-WaitLSNWakeup-fast-path-check-for-InvalidXLog.patch)
  download | inline diff:
From de673ec025074cd95ad4a4e53e2c26fcc14d5a4a Mon Sep 17 00:00:00 2001
From: alterego655 <[email protected]>
Date: Fri, 14 Nov 2025 09:34:03 +0800
Subject: [PATCH v1] Fix WaitLSNWakeup() fast-path check for InvalidXLogRecPtr

WaitLSNWakeup() incorrectly returned early when called with
InvalidXLogRecPtr (meaning "wake all waiters"), because the fast-path
check compared minWaitedLSN > 0 without validating currentLSN first.
This caused WAIT FOR LSN commands to wait indefinitely during standby
promotion until random signals woke them.

Add XLogRecPtrIsValid() check before the comparison so InvalidXLogRecPtr
bypasses the fast-path and wakes all waiters immediately.
---
 src/backend/access/transam/xlogwait.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 34fa41ed9b2..78de93db47f 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -270,8 +270,12 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN)
 
 	Assert(i >= 0 && i < (int) WAIT_LSN_TYPE_COUNT);
 
-	/* Fast path check */
-	if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
+	/*
+	 * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
+	 * "wake all waiters" (e.g., during promotion when recovery ends).
+	 */
+	if (XLogRecPtrIsValid(currentLSN) &&
+		pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
 		return;
 
 	wakeupWaiters(lsnType, currentLSN);
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-13 20:32                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-11-14 01:49                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-15 10:29                                                                     ` Alexander Korotkov <[email protected]>
  2025-11-16 13:25                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-15 10:29 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Yura Sokolov <[email protected]>

Hi, Xuneng!

On Fri, Nov 14, 2025 at 3:50 AM Xuneng Zhou <[email protected]> wrote:
>
> On Fri, Nov 14, 2025 at 4:32 AM Tomas Vondra <[email protected]> wrote:
> >
> > On 11/5/25 10:51, Alexander Korotkov wrote:
> > > Hi!
> > >
> > > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > >> On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > >>> On 2025-Nov-03, Alexander Korotkov wrote:
> > >>>
> > >>>> I'd like to give this subject another chance for pg19.  I'm going to
> > >>>> push this if no objections.
> > >>>
> > >>> Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > >>
> > >> FWIW, I appreciate such splits. Even if the functionality isn't usable
> > >> independently, it's still different type of code that's affected. And the
> > >> patches are each big enough to make that worthwhile for easier review.
> > >
> > > Thank you for the feedback, pushed.
> > >
> >
> > Hi,
> >
> > The new TAP test 049_wait_for_lsn.pl introduced by this commit, because
> > it takes a long time - about 65 seconds on my laptop. That's about 25%
> > of the whole src/test/recovery, more than any other test.
> >
> > And most of the time there's nothing happening - these are the two log
> > messages showing the 60-second wait:
> >
> > 2025-11-13 21:12:39.949 CET checkpointer[562597] LOG:  checkpoint
> > complete: wrote 9 buffers (7.0%), wrote 3 SLRU buffers; 0 WAL file(s)
> > added, 0 removed, 2 recycled; write=0.906 s, sync=0.001 s, total=0.907
> > s; sync files=0, longest=0.000 s, average=0.000 s; distance=32768 kB,
> > estimate=32768 kB; lsn=0/040000B8, redo lsn=0/04000060
> >
> > 2025-11-13 21:13:38.994 CET client backend[562727] 049_wait_for_lsn.pl
> > ERROR:  recovery is not in progress
> >
> > So there's a checkpoint, 60 seconds of nothing, and then a failure. I
> > haven't looked into why it waits for 1 minute exactly, but adding 60
> > seconds to check-world is somewhat annoying.
>
> Thanks for looking into this!
>
> I did a quick analysis for this prolonged waiting:
>
> In WaitLSNWakeup() (xlogwait.c:267), the fast-path check incorrectly
> handled InvalidXLogRecPtr:
> /* Fast path check */
> if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
>     return;  // Issue: Returns early when currentLSN = 0
>
> When currentLSN = InvalidXLogRecPtr (0), meaning "wake all waiters",
> the check compared:
> - minWaitedLSN (e.g., 0x570CC048) > 0 → TRUE
> - Result: function returned early without waking anyone
>
> When It Happened
> During standby promotion, xlog.c:6246 calls:
>
> WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
>
> This should wake all LSN waiters, but the bug prevented it. WAIT FOR
> LSN commands could wait indefinitely. Test 049_wait_for_lsn.pl took 68
> seconds instead of ~9 seconds.
>
> if the above analysis is sound, the fix could be like:
>
> Proposed fix:
> Added a validity check before the comparison:
> /*
>  * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
>  * "wake all waiters" (e.g., during promotion when recovery ends).
>  */
> if (XLogRecPtrIsValid(currentLSN) &&
>     pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
>     return;
>
> Result:
> Test time: 68s → 9s
> WAIT FOR LSN exits immediately on promotion (62ms vs 60s)
>
> > While at it, I noticed a couple comments refer to WaitForLSNReplay, but
> > but I think that got renamed simply to WaitForLSN.
>
> Please check the attached patch for replacing them.

Thank you so much for your patches!
Pushed with minor corrections.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-13 20:32                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-11-14 01:49                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-15 10:29                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
@ 2025-11-16 13:25                                                                       ` Xuneng Zhou <[email protected]>
  2025-11-16 15:20                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 527+ messages in thread

From: Xuneng Zhou @ 2025-11-16 13:25 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Yura Sokolov <[email protected]>

Hi Alexander,

On Sat, Nov 15, 2025 at 6:29 PM Alexander Korotkov <[email protected]> wrote:
>
> Hi, Xuneng!
>
> On Fri, Nov 14, 2025 at 3:50 AM Xuneng Zhou <[email protected]> wrote:
> >
> > On Fri, Nov 14, 2025 at 4:32 AM Tomas Vondra <[email protected]> wrote:
> > >
> > > On 11/5/25 10:51, Alexander Korotkov wrote:
> > > > Hi!
> > > >
> > > > On Mon, Nov 3, 2025 at 5:13 PM Andres Freund <[email protected]> wrote:
> > > >> On 2025-11-03 16:06:58 +0100, Álvaro Herrera wrote:
> > > >>> On 2025-Nov-03, Alexander Korotkov wrote:
> > > >>>
> > > >>>> I'd like to give this subject another chance for pg19.  I'm going to
> > > >>>> push this if no objections.
> > > >>>
> > > >>> Sure.  I don't understand why patches 0002 and 0003 are separate though.
> > > >>
> > > >> FWIW, I appreciate such splits. Even if the functionality isn't usable
> > > >> independently, it's still different type of code that's affected. And the
> > > >> patches are each big enough to make that worthwhile for easier review.
> > > >
> > > > Thank you for the feedback, pushed.
> > > >
> > >
> > > Hi,
> > >
> > > The new TAP test 049_wait_for_lsn.pl introduced by this commit, because
> > > it takes a long time - about 65 seconds on my laptop. That's about 25%
> > > of the whole src/test/recovery, more than any other test.
> > >
> > > And most of the time there's nothing happening - these are the two log
> > > messages showing the 60-second wait:
> > >
> > > 2025-11-13 21:12:39.949 CET checkpointer[562597] LOG:  checkpoint
> > > complete: wrote 9 buffers (7.0%), wrote 3 SLRU buffers; 0 WAL file(s)
> > > added, 0 removed, 2 recycled; write=0.906 s, sync=0.001 s, total=0.907
> > > s; sync files=0, longest=0.000 s, average=0.000 s; distance=32768 kB,
> > > estimate=32768 kB; lsn=0/040000B8, redo lsn=0/04000060
> > >
> > > 2025-11-13 21:13:38.994 CET client backend[562727] 049_wait_for_lsn.pl
> > > ERROR:  recovery is not in progress
> > >
> > > So there's a checkpoint, 60 seconds of nothing, and then a failure. I
> > > haven't looked into why it waits for 1 minute exactly, but adding 60
> > > seconds to check-world is somewhat annoying.
> >
> > Thanks for looking into this!
> >
> > I did a quick analysis for this prolonged waiting:
> >
> > In WaitLSNWakeup() (xlogwait.c:267), the fast-path check incorrectly
> > handled InvalidXLogRecPtr:
> > /* Fast path check */
> > if (pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
> >     return;  // Issue: Returns early when currentLSN = 0
> >
> > When currentLSN = InvalidXLogRecPtr (0), meaning "wake all waiters",
> > the check compared:
> > - minWaitedLSN (e.g., 0x570CC048) > 0 → TRUE
> > - Result: function returned early without waking anyone
> >
> > When It Happened
> > During standby promotion, xlog.c:6246 calls:
> >
> > WaitLSNWakeup(WAIT_LSN_TYPE_REPLAY, InvalidXLogRecPtr);
> >
> > This should wake all LSN waiters, but the bug prevented it. WAIT FOR
> > LSN commands could wait indefinitely. Test 049_wait_for_lsn.pl took 68
> > seconds instead of ~9 seconds.
> >
> > if the above analysis is sound, the fix could be like:
> >
> > Proposed fix:
> > Added a validity check before the comparison:
> > /*
> >  * Fast path check.  Skip if currentLSN is InvalidXLogRecPtr, which means
> >  * "wake all waiters" (e.g., during promotion when recovery ends).
> >  */
> > if (XLogRecPtrIsValid(currentLSN) &&
> >     pg_atomic_read_u64(&waitLSNState->minWaitedLSN[i]) > currentLSN)
> >     return;
> >
> > Result:
> > Test time: 68s → 9s
> > WAIT FOR LSN exits immediately on promotion (62ms vs 60s)
> >
> > > While at it, I noticed a couple comments refer to WaitForLSNReplay, but
> > > but I think that got renamed simply to WaitForLSN.
> >
> > Please check the attached patch for replacing them.
>
> Thank you so much for your patches!
> Pushed with minor corrections.

Thanks for pushing! It appears I should be running pgindent more regularly :).


--
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
  2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-13 20:32                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-11-14 01:49                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-11-15 10:29                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-11-16 13:25                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-11-16 15:20                                                                         ` Alexander Korotkov <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Alexander Korotkov @ 2025-11-16 15:20 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Andres Freund <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Yura Sokolov <[email protected]>

On Sun, Nov 16, 2025 at 3:25 PM Xuneng Zhou <[email protected]> wrote:
> On Sat, Nov 15, 2025 at 6:29 PM Alexander Korotkov <[email protected]> wrote:
> > Thank you so much for your patches!
> > Pushed with minor corrections.
>
> Thanks for pushing! It appears I should be running pgindent more regularly :).

Thank you.  pgindent is not a problem for me, cause I anyway run it
every time before pushing a patch.  But yes, if you make it a habit to
run pgindent every time before publishing a patch, it would become
cleaner.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
@ 2025-12-16 05:48                                       ` Chao Li <[email protected]>
  2025-12-16 06:42                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  1 sibling, 1 reply; 527+ messages in thread

From: Chao Li @ 2025-12-16 05:48 UTC (permalink / raw)
  To: Xuneng Zhou <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]



> On Oct 4, 2025, at 09:35, Xuneng Zhou <[email protected]> wrote:
> 
> Hi,
> 
> On Sun, Sep 28, 2025 at 5:02 PM Xuneng Zhou <[email protected]> wrote:
>> 
>> Hi,
>> 
>> On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
>>> 
>>> Hi Álvaro,
>>> 
>>> Thanks for your review.
>>> 
>>> On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
>>>> 
>>>> On 2025-Sep-15, Alexander Korotkov wrote:
>>>> 
>>>>>> It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
>>>>>> PUBLICATION - all use minimal grammar rules that produce generic
>>>>>> option lists, with the actual interpretation done in their respective
>>>>>> implementation files. The moderate complexity in wait.c seems
>>>>>> acceptable.
>>>> 
>>>> Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
>>>> lists of DefElem (a name optionally followed by a value) instead of
>>>> individual scattered elements that must later be matched up.  Why not
>>>> use utility_option_list instead and then loop on the list of DefElems?
>>>> It'd be a lot simpler.
>>> 
>>> I took a look at commands like VACUUM and EXPLAIN and they do follow
>>> this pattern. v11 will make use of utility_option_list.
>>> 
>>>> Also, we've found that failing to surround the options by parens leads
>>>> to pain down the road, so maybe add that.  Given that the LSN seems to
>>>> be mandatory, maybe make it something like
>>>> 
>>>> WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
>>>> 
>>>> This requires that you make LSN a keyword, albeit unreserved.  Or you
>>>> could make it
>>>> WAIT FOR Ident [the rest]
>>>> and then ensure in C that the identifier matches the word LSN, such as
>>>> we do for "permissive" and "restrictive" in
>>>> RowSecurityDefaultPermissive.
>>> 
>>> Shall make LSN an unreserved keyword as well.
>> 
>> Here's the updated v11.  Many thanks Jian for off-list discussions and review.
> 
> v12 removed unused
> +WaitStmt
> +WaitStmtParam in pgindent/typedefs.list.
> 
> Best,
> Xuneng
> <v12-0001-Implement-WAIT-FOR-command.patch>

I just tried to review v12 but failed to “git am”. Can you please rebase the change?

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
  2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
  2025-12-16 05:48                                       ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
@ 2025-12-16 06:42                                         ` Xuneng Zhou <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Xuneng Zhou @ 2025-12-16 06:42 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Alexander Korotkov <[email protected]>; jian he <[email protected]>; Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hi,

On Tue, Dec 16, 2025 at 1:49 PM Chao Li <[email protected]> wrote:
>
>
>
> > On Oct 4, 2025, at 09:35, Xuneng Zhou <[email protected]> wrote:
> >
> > Hi,
> >
> > On Sun, Sep 28, 2025 at 5:02 PM Xuneng Zhou <[email protected]> wrote:
> >>
> >> Hi,
> >>
> >> On Fri, Sep 26, 2025 at 7:22 PM Xuneng Zhou <[email protected]> wrote:
> >>>
> >>> Hi Álvaro,
> >>>
> >>> Thanks for your review.
> >>>
> >>> On Tue, Sep 16, 2025 at 4:24 AM Álvaro Herrera <[email protected]> wrote:
> >>>>
> >>>> On 2025-Sep-15, Alexander Korotkov wrote:
> >>>>
> >>>>>> It's LGTM. The same pattern is observed in VACUUM, EXPLAIN, and CREATE
> >>>>>> PUBLICATION - all use minimal grammar rules that produce generic
> >>>>>> option lists, with the actual interpretation done in their respective
> >>>>>> implementation files. The moderate complexity in wait.c seems
> >>>>>> acceptable.
> >>>>
> >>>> Actually I find the code in ExecWaitStmt pretty unusual.  We tend to use
> >>>> lists of DefElem (a name optionally followed by a value) instead of
> >>>> individual scattered elements that must later be matched up.  Why not
> >>>> use utility_option_list instead and then loop on the list of DefElems?
> >>>> It'd be a lot simpler.
> >>>
> >>> I took a look at commands like VACUUM and EXPLAIN and they do follow
> >>> this pattern. v11 will make use of utility_option_list.
> >>>
> >>>> Also, we've found that failing to surround the options by parens leads
> >>>> to pain down the road, so maybe add that.  Given that the LSN seems to
> >>>> be mandatory, maybe make it something like
> >>>>
> >>>> WAIT FOR LSN 'xy/zzy' [ WITH ( utility_option_list ) ]
> >>>>
> >>>> This requires that you make LSN a keyword, albeit unreserved.  Or you
> >>>> could make it
> >>>> WAIT FOR Ident [the rest]
> >>>> and then ensure in C that the identifier matches the word LSN, such as
> >>>> we do for "permissive" and "restrictive" in
> >>>> RowSecurityDefaultPermissive.
> >>>
> >>> Shall make LSN an unreserved keyword as well.
> >>
> >> Here's the updated v11.  Many thanks Jian for off-list discussions and review.
> >
> > v12 removed unused
> > +WaitStmt
> > +WaitStmtParam in pgindent/typedefs.list.
> >
> > Best,
> > Xuneng
> > <v12-0001-Implement-WAIT-FOR-command.patch>
>
> I just tried to review v12 but failed to “git am”. Can you please rebase the change?
>

Thanks for looking into this.

That series of patches implementing the WAIT FOR REPLAY command was
applied last month (8af3ae0d , 447aae13, 3b4e53a0, a1f7f91b) in its
version 20. The current v6 patch set [1] [2] primarily extends the
WAIT FOR functionality to support waiting for flush and write LSNs on
a replica by adding a MODE parameter [3]. This made me wonder whether
it would be more appropriate to start a new thread for the extension,
though it is still part of the same WAIT FOR command.

[1] https://commitfest.postgresql.org/patch/6265/
[2] https://www.postgresql.org/message-id/[email protected]...
[3] https://www.postgresql.org/message-id/[email protected]...

-- 
Best,
Xuneng





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
  2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
@ 2025-08-08 06:54                   ` Alexander Korotkov <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: Alexander Korotkov @ 2025-08-08 06:54 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Yura Sokolov <[email protected]>; [email protected]

Hello, Álvaro!

On Wed, Aug 6, 2025 at 6:01 AM Álvaro Herrera <[email protected]> wrote:
>
> On 2025-Apr-29, Alexander Korotkov wrote:
>
> > > 11) WaitLSNProcInfo / WaitLSNState
> > >
> > > Does this need to be exposed in xlogwait.h? These structs seem private
> > > to xlogwait.c, so maybe declare it there?
> >
> > Hmm, I don't remember why I moved them to xlogwait.h.  OK, moved them
> > back to xlogwait.c.
>
> This change made the code no longer compile, because
> WaitLSNState->minWaitedLSN is used in xlogrecovery.c which no longer has
> access to the field definition.  A rebased version with that change
> reverted is attached.

Thank you!  The rebased version looks correct for me.

------
Regards,
Alexander Korotkov
Supabase





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* Re: Implement waiting for wal lsn replay: reloaded
  2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
  2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
  2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
@ 2025-03-16 13:32             ` vignesh C <[email protected]>
  1 sibling, 0 replies; 527+ messages in thread

From: vignesh C @ 2025-03-16 13:32 UTC (permalink / raw)
  To: Yura Sokolov <[email protected]>; +Cc: [email protected]

On Wed, 12 Mar 2025 at 20:14, Yura Sokolov <[email protected]> wrote:
>
> Otherwise v6 is just rebased v5.

I noticed that Tomas's comments from [1] are not yet addressed, I have
changed the commitfest status to Waiting on Author, please address
them and update it to Needs review.
[1] - https://www.postgresql.org/message-id/[email protected]

Regards,
Vignesh





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH 1/2] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--m4rixdodkhrlzpzv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-Apply-the-same-technique-to-PrintNewControlValues-to.patch"



^ permalink  raw  reply  [nested|flat] 527+ messages in thread

* [PATCH] Split out entry names in pg_resetwal
@ 2025-11-14 15:25 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 527+ messages in thread

From: Álvaro Herrera @ 2025-11-14 15:25 UTC (permalink / raw)

---
 src/bin/pg_resetwal/entries.h     |  62 ++++++++++++++
 src/bin/pg_resetwal/nls.mk        |   4 +-
 src/bin/pg_resetwal/pg_resetwal.c | 132 ++++++++++++++++--------------
 3 files changed, 134 insertions(+), 64 deletions(-)
 create mode 100644 src/bin/pg_resetwal/entries.h

diff --git a/src/bin/pg_resetwal/entries.h b/src/bin/pg_resetwal/entries.h
new file mode 100644
index 00000000000..6b7686da83a
--- /dev/null
+++ b/src/bin/pg_resetwal/entries.h
@@ -0,0 +1,62 @@
+CONTROLDATA_LINE("pg_control version number",
+				 "%u", ControlFile.pg_control_version)
+CONTROLDATA_LINE("Catalog version number",
+				 "%u", ControlFile.catalog_version_no)
+CONTROLDATA_LINE("Database system identifier",
+				 "%" PRIu64, ControlFile.system_identifier)
+CONTROLDATA_LINE("Latest checkpoint's TimeLineID",
+				 "%u", ControlFile.checkPointCopy.ThisTimeLineID)
+CONTROLDATA_LINE("Latest checkpoint's full_page_writes",
+				 "%s", (ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")))
+CONTROLDATA_LINE("Latest checkpoint's NextXID",
+				 "%u:%u", EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid), XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid))
+CONTROLDATA_LINE("Latest checkpoint's NextOID",
+				 "%u", ControlFile.checkPointCopy.nextOid)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiXactId",
+				 "%u", ControlFile.checkPointCopy.nextMulti)
+CONTROLDATA_LINE("Latest checkpoint's NextMultiOffset",
+				 "%" PRIu64, ControlFile.checkPointCopy.nextMultiOffset)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID",
+				 "%u", ControlFile.checkPointCopy.oldestXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestXID's DB",
+				 "%u", ControlFile.checkPointCopy.oldestXidDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestActiveXID",
+				 "%u", ControlFile.checkPointCopy.oldestActiveXid)
+CONTROLDATA_LINE("Latest checkpoint's oldestMultiXid",
+				 "%u", ControlFile.checkPointCopy.oldestMulti)
+CONTROLDATA_LINE("Latest checkpoint's oldestMulti's DB",
+				 "%u", ControlFile.checkPointCopy.oldestMultiDB)
+CONTROLDATA_LINE("Latest checkpoint's oldestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.oldestCommitTsXid)
+CONTROLDATA_LINE("Latest checkpoint's newestCommitTsXid",
+				 "%u", ControlFile.checkPointCopy.newestCommitTsXid)
+CONTROLDATA_LINE("Maximum data alignment",
+				 "%u", ControlFile.maxAlign)
+CONTROLDATA_LINE("Database block size",
+				 "%u", ControlFile.blcksz)
+CONTROLDATA_LINE("Blocks per segment of large relation",
+				 "%u", ControlFile.relseg_size)
+CONTROLDATA_LINE("Pages per SLRU segment",
+				 "%u", ControlFile.slru_pages_per_segment)
+CONTROLDATA_LINE("WAL block size",
+				 "%u", ControlFile.xlog_blcksz)
+CONTROLDATA_LINE("Bytes per WAL segment",
+				 "%u", ControlFile.xlog_seg_size)
+CONTROLDATA_LINE("Maximum length of identifiers",
+				 "%u", ControlFile.nameDataLen)
+CONTROLDATA_LINE("Maximum columns in an index",
+				 "%u", ControlFile.indexMaxKeys)
+CONTROLDATA_LINE("Maximum size of a TOAST chunk",
+				 "%u", ControlFile.toast_max_chunk_size)
+CONTROLDATA_LINE("Size of a large-object chunk",
+				 "%u", ControlFile.loblksize)
+
+/* This is no longer configurable, but users may still expect to see it: */
+CONTROLDATA_LINE("Date/time type storage",
+				 "%s", _("64-bit integers"))
+CONTROLDATA_LINE("Float8 argument passing",
+				 "%s", ControlFile.float8ByVal ? _("by value") : _("by reference"))
+CONTROLDATA_LINE("Data page checksum version",
+				 "%u", ControlFile.data_checksum_version)
+CONTROLDATA_LINE("Default char data signedness",
+				 "%s", ControlFile.default_char_signedness ? _("signed") : _("unsigned"))
diff --git a/src/bin/pg_resetwal/nls.mk b/src/bin/pg_resetwal/nls.mk
index 694d5420a29..8dabee72bef 100644
--- a/src/bin/pg_resetwal/nls.mk
+++ b/src/bin/pg_resetwal/nls.mk
@@ -2,10 +2,12 @@
 CATALOG_NAME     = pg_resetwal
 GETTEXT_FILES    = $(FRONTEND_COMMON_GETTEXT_FILES) \
                    pg_resetwal.c \
+                   entries.h \
                    ../../common/controldata_utils.c \
                    ../../common/fe_memutils.c \
                    ../../common/file_utils.c \
                    ../../common/restricted_token.c \
                    ../../fe_utils/option_utils.c
-GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+				   CONTROLDATA_LINE:1
 GETTEXT_FLAGS    = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 431b83a67da..fb17b6f80e0 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -57,6 +57,7 @@
 #include "fe_utils/option_utils.h"
 #include "fe_utils/version.h"
 #include "getopt_long.h"
+#include "mb/pg_wchar.h"
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
@@ -114,6 +115,7 @@ static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static int	internal_wcswidth(const char *pwcs, size_t len, int encoding);
 static uint32 strtouint32_strict(const char *restrict s, char **restrict endptr, int base);
 static uint64 strtouint64_strict(const char *restrict s, char **restrict endptr, int base);
 
@@ -753,74 +755,48 @@ GuessControlValues(void)
 static void
 PrintControlValues(bool guessed)
 {
+	int			encoding = pg_get_encoding_from_locale(NULL, true);
+	int			maxlen = 0;
+	int			thislen;
+
 	if (guessed)
 		printf(_("Guessed pg_control values:\n\n"));
 	else
 		printf(_("Current pg_control values:\n\n"));
 
-	printf(_("pg_control version number:            %u\n"),
-		   ControlFile.pg_control_version);
-	printf(_("Catalog version number:               %u\n"),
-		   ControlFile.catalog_version_no);
-	printf(_("Database system identifier:           %" PRIu64 "\n"),
-		   ControlFile.system_identifier);
-	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
-		   ControlFile.checkPointCopy.ThisTimeLineID);
-	printf(_("Latest checkpoint's full_page_writes: %s\n"),
-		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
-	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
-		   XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-	printf(_("Latest checkpoint's NextOID:          %u\n"),
-		   ControlFile.checkPointCopy.nextOid);
-	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
-		   ControlFile.checkPointCopy.nextMulti);
-	printf(_("Latest checkpoint's NextMultiOffset:  %" PRIu64 "\n"),
-		   ControlFile.checkPointCopy.nextMultiOffset);
-	printf(_("Latest checkpoint's oldestXID:        %u\n"),
-		   ControlFile.checkPointCopy.oldestXid);
-	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
-		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
-		   ControlFile.checkPointCopy.oldestActiveXid);
-	printf(_("Latest checkpoint's oldestMultiXid:   %u\n"),
-		   ControlFile.checkPointCopy.oldestMulti);
-	printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
-		   ControlFile.checkPointCopy.oldestMultiDB);
-	printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.oldestCommitTsXid);
-	printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
-		   ControlFile.checkPointCopy.newestCommitTsXid);
-	printf(_("Maximum data alignment:               %u\n"),
-		   ControlFile.maxAlign);
-	/* we don't print floatFormat since can't say much useful about it */
-	printf(_("Database block size:                  %u\n"),
-		   ControlFile.blcksz);
-	printf(_("Blocks per segment of large relation: %u\n"),
-		   ControlFile.relseg_size);
-	printf(_("Pages per SLRU segment:               %u\n"),
-		   ControlFile.slru_pages_per_segment);
-	printf(_("WAL block size:                       %u\n"),
-		   ControlFile.xlog_blcksz);
-	printf(_("Bytes per WAL segment:                %u\n"),
-		   ControlFile.xlog_seg_size);
-	printf(_("Maximum length of identifiers:        %u\n"),
-		   ControlFile.nameDataLen);
-	printf(_("Maximum columns in an index:          %u\n"),
-		   ControlFile.indexMaxKeys);
-	printf(_("Maximum size of a TOAST chunk:        %u\n"),
-		   ControlFile.toast_max_chunk_size);
-	printf(_("Size of a large-object chunk:         %u\n"),
-		   ControlFile.loblksize);
-	/* This is no longer configurable, but users may still expect to see it: */
-	printf(_("Date/time type storage:               %s\n"),
-		   _("64-bit integers"));
-	printf(_("Float8 argument passing:              %s\n"),
-		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
-	printf(_("Data page checksum version:           %u\n"),
-		   ControlFile.data_checksum_version);
-	printf(_("Default char data signedness:         %s\n"),
-		   (ControlFile.default_char_signedness ? _("signed") : _("unsigned")));
+	/*
+	 * First, determine the maximum length of the description of all entries,
+	 * some or all of which might be translated.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	thislen = internal_wcswidth(_(description),			\
+								strlen(_(description)),	\
+								encoding);				\
+	if (thislen > maxlen)								\
+		maxlen = thislen;
+#include "entries.h"
+#undef CONTROLDATA_LINE
+
+	/*
+	 * Print each line: the possibly-translated description, then some padding
+	 * spaces according to its display width, then the value.
+	 */
+#define CONTROLDATA_LINE(description, fmt, ...)			\
+	{													\
+		int		thisstrlen;								\
+														\
+		thisstrlen = strlen(_(description));			\
+		thislen = internal_wcswidth(_(description),		\
+									thisstrlen,			\
+									encoding);			\
+		printf("%s:%*s" fmt "\n",						\
+			   _(description),							\
+			   maxlen - thislen + 2,					\
+			   " ",										\
+			   __VA_ARGS__);							\
+	}
+#include "entries.h"
+#undef CONTROLDATA_LINE
 }
 
 
@@ -1238,6 +1214,36 @@ usage(void)
 	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
 
+/*
+ * Measure the display length of a single-line string in the given encoding.
+ *
+ * Similar to pg_wcswidth, written without dependency on libpq.
+ */
+static int
+internal_wcswidth(const char *pwcs, size_t len, int encoding)
+{
+	int			width = 0;
+
+	while (len > 0)
+	{
+		int			chlen,
+					chwidth;
+
+		chlen = pg_encoding_mblen(encoding, pwcs);
+		if (len < (size_t) chlen)
+			break;				/* Invalid string */
+
+		chwidth = pg_encoding_dsplen(encoding, pwcs);
+		if (chwidth > 0)
+			width += chwidth;
+
+		pwcs += chlen;
+		len -= chlen;
+	}
+	return width;
+}
+
+
 /*
  * strtouint32_strict -- like strtoul(), but returns uint32 and doesn't accept
  * negative values
-- 
2.47.3


--6po7oomtfoqj5cjo--





^ permalink  raw  reply  [nested|flat] 527+ messages in thread


end of thread, other threads:[~2026-07-09 12:24 UTC | newest]

Thread overview: 527+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-07-01 02:31 [PATCH 4/5] Catcache pruning feature. Kyotaro Horiguchi <[email protected]>
2023-07-28 22:56 [PATCH v4 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v2 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v9 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v3 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v3 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v6 2/2] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v3 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v8 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v9 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v8 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v6 2/2] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v3 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v9 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v1 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v7 2/2] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v2 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v9 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v5 2/2] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v5 2/2] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v9 4/4] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2023-07-28 22:56 [PATCH v4 1/1] allow syncfs in frontend utilities Nathan Bossart <[email protected]>
2024-11-27 04:08 Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2024-12-04 11:12 ` Re: Implement waiting for wal lsn replay: reloaded Kirill Reshke <[email protected]>
2025-02-06 07:42   ` Re: Implement waiting for wal lsn replay: reloaded Andrei Lepikhov <[email protected]>
2025-02-06 08:31 ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
2025-02-16 21:27   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-02-28 13:03     ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
2025-02-28 13:55       ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
2025-03-10 11:30         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-03-12 14:44           ` Re: Implement waiting for wal lsn replay: reloaded Yura Sokolov <[email protected]>
2025-03-13 14:15             ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
2025-04-29 11:27               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-08-05 13:47                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
2025-08-07 15:00                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-08-08 07:08                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-08-09 10:52                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-08-09 11:27                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-08-27 15:54                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-09-13 19:31                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-09-14 13:51                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-09-15 18:59                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-09-15 20:24                               ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
2025-09-26 11:22                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-09-28 09:02                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-04 01:35                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-14 13:03                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-15 00:23                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-15 08:40                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-15 08:51                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
2025-10-15 12:48                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-16 07:11                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-10-23 10:46                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-10-23 12:58                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-02 06:24                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-03 02:20                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-03 11:46                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-03 15:06                                                           ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
2025-11-03 15:13                                                             ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2025-11-05 09:51                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-05 14:03                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-07 22:02                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-16 12:08                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-16 13:30                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-12 07:19                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-16 12:36                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-16 14:01                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-20 12:43                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-25 11:51                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-01 04:33                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-02 03:08                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-02 10:10                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-16 03:28                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-16 04:46                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-18 10:38                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-12-18 12:24                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-18 12:25                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-12-19 02:49                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-20 21:09                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-12-21 04:37                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-22 07:56                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-25 11:13                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-12-25 12:52                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-25 16:34                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-12-26 00:31                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-26 08:24                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
2025-12-26 16:15                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-30 02:12                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-30 02:42                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-12-30 03:14                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
2025-12-30 03:24                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
2025-12-30 06:19                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-01 17:16                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Álvaro Herrera <[email protected]>
2026-01-01 23:42                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-01-02 09:17                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-02 22:53                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-01-03 16:10                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-06 05:42                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Thomas Munro <[email protected]>
2026-01-06 07:29                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-06 11:54                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-01-06 13:12                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-06 15:34                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-01-06 15:58                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-06 17:04                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-06 15:53                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-07 00:32                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-01-07 04:08                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-08 14:19                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-01-08 16:29                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-08 20:42                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-01-09 13:44                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-10 04:47                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-12 06:53                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-20 01:28                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-27 01:14                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-29 07:47                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-05 12:31                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-06 03:26                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-06 06:01                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-06 07:15                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-06 19:49                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-07 01:52                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
2026-04-07 02:08                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Tom Lane <[email protected]>
2026-04-07 02:28                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-07 03:07                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-04-07 03:31                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-04-07 04:02                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-07 12:52                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-07 13:05                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-07 13:18                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-04-07 13:46                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-07 13:52                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-04-07 14:29                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-07 15:55                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-07 23:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-08 00:20                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-08 00:50                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-04-08 07:31                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-07 12:59                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-07 23:23                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-08 03:23                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-08 04:59                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-09 15:21                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-09 16:18                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Andres Freund <[email protected]>
2026-04-10 03:59                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-10 06:13                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-20 18:45                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-21 04:03                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-04-28 21:01                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-05-01 02:44                                                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-07-06 11:04                                                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Heikki Linnakangas <[email protected]>
2026-07-06 13:49                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-07-06 14:17                                                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-07-08 12:08                                                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-07-08 17:38                                                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-07-09 04:53                                                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-07-09 12:24                                                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-07-07 15:25                                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-04-15 08:30                                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-05-19 20:00                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Lakhin <[email protected]>
2026-05-20 03:30                                                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-05-20 05:18                                                                                                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-05-20 12:16                                                                                                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-05-23 17:09                                                                                                                                                           ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-05-23 18:40                                                                                                                                                             ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-05-25 09:00                                                                                                                                                               ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2026-05-26 01:48                                                                                                                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-05-26 15:53                                                                                                                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2026-01-07 08:06                                                                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-13 20:32                                                                 ` Re: Implement waiting for wal lsn replay: reloaded Tomas Vondra <[email protected]>
2025-11-14 01:49                                                                   ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-15 10:29                                                                     ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-11-16 13:25                                                                       ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-11-16 15:20                                                                         ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-12-16 05:48                                       ` Re: Implement waiting for wal lsn replay: reloaded Chao Li <[email protected]>
2025-12-16 06:42                                         ` Re: Implement waiting for wal lsn replay: reloaded Xuneng Zhou <[email protected]>
2025-08-08 06:54                   ` Re: Implement waiting for wal lsn replay: reloaded Alexander Korotkov <[email protected]>
2025-03-16 13:32             ` Re: Implement waiting for wal lsn replay: reloaded vignesh C <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH 1/2] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>
2025-11-14 15:25 [PATCH] Split out entry names in pg_resetwal Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox