0003-Refactor-syncrep-parsing-to-represent-bare-standby-l.patch

application/octet-stream

Filename: 0003-Refactor-syncrep-parsing-to-represent-bare-standby-l.patch
Type: application/octet-stream
Part: 2
Message: Re: synchronized_standby_slots behavior inconsistent with quorum-based synchronous replication

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0003
Subject: Refactor syncrep parsing to represent bare standby lists explicitly
File+
src/backend/replication/slot.c 4 63
src/backend/replication/syncrep.c 4 0
src/backend/replication/syncrep_gram.y 1 1
src/include/replication/syncrep.h 1 0
From d0e8c01d367ed577b61785769f89d586e339c763 Mon Sep 17 00:00:00 2001
From: Zhijie Hou <houzj.fnst@fujitsu.com>
Date: Tue, 14 Apr 2026 12:14:52 +0000
Subject: [PATCH 3/3] Refactor syncrep parsing to represent bare standby lists
 explicitly

The syncrep parser currently reduces a simple list form to FIRST 1
(SYNC_REP_PRIORITY). That is acceptable for synchronous_standby_names, but
it loses information about whether FIRST was explicitly written.

Introduce SYNC_REP_DEFAULT to represent the bare list form parsed from
standby_list. This allows callers to distinguish:

- explicit priority syntax (FIRST N (...) or N (...))
- quorum syntax (ANY N (...))
- simple list syntax without FIRST/ANY

With this change:

- syncrep grammar emits SYNC_REP_DEFAULT for bare standby lists
- check_synchronous_standby_names() maps SYNC_REP_DEFAULT to
  SYNC_REP_PRIORITY, preserving existing synchronous_standby_names behavior
- check_synchronized_standby_slots() can directly interpret
  SYNC_REP_DEFAULT as plain-list semantics (all listed slots), and the
  localized FIRST/N disambiguation helper is removed

This keeps existing semantics intact while making parser output precise and
reusable across both GUCs.
---
 src/backend/replication/slot.c         | 67 ++------------------------
 src/backend/replication/syncrep.c      |  4 ++
 src/backend/replication/syncrep_gram.y |  2 +-
 src/include/replication/syncrep.h      |  1 +
 4 files changed, 10 insertions(+), 64 deletions(-)

diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 38826fa4a41..f696d90cb2e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -222,7 +222,6 @@ static void ReplicationSlotDropPtr(ReplicationSlot *slot);
 static void RestoreSlotFromDisk(const char *name);
 static void CreateSlotOnDisk(ReplicationSlot *slot);
 static void SaveSlotToPath(ReplicationSlot *slot, const char *dir, int elevel);
-static bool IsPrioritySyncStandbySlotsSyntax(const char *value);
 
 /*
  * Register shared memory space needed for replication slots.
@@ -2997,52 +2996,6 @@ GetSlotInvalidationCauseName(ReplicationSlotInvalidationCause cause)
 	return "none";				/* to keep compiler quiet */
 }
 
-/*
- * Return true if value starts with explicit priority syntax:
- *
- *   FIRST N (...)
- *   N (...)
- *
- * This is used to distinguish explicit priority syntax from simple list
- * syntax whose first slot name may start with "first".
- */
-static bool
-IsPrioritySyncStandbySlotsSyntax(const char *value)
-{
-	const char *p = value;
-
-	/* Skip leading whitespace */
-	while (*p && isspace((unsigned char) *p))
-		p++;
-
-	/*
-	 * Accept either explicit FIRST N (...) or bare N (...) priority syntax.
-	 * If the input starts with FIRST, it must be followed by whitespace.
-	 */
-	if (pg_strncasecmp(p, "FIRST", 5) == 0)
-	{
-		p += 5;
-
-		if (!isspace((unsigned char) *p))
-			return false;
-
-		while (*p && isspace((unsigned char) *p))
-			p++;
-	}
-
-	if (!isdigit((unsigned char) *p))
-		return false;
-
-	while (*p && isdigit((unsigned char) *p))
-		p++;
-
-	/* Explicit priority syntax then requires a parenthesized member list */
-	while (*p && isspace((unsigned char) *p))
-		p++;
-
-	return (*p == '(');
-}
-
 /*
  * GUC check_hook for synchronized_standby_slots
  *
@@ -3109,27 +3062,15 @@ check_synchronized_standby_slots(char **newval, void **extra, GucSource source)
 		}
 
 		/*
-		 * When using simple list syntax (e.g., "slot1, slot2"), the parser
-		 * returns num_sync=1 and SYNC_REP_PRIORITY. We interpret this as
-		 * "wait for ALL slots" by setting num_sync to nmembers.
+		 * When using simple list syntax (e.g., "slot1, slot2"), we interpret
+		 * this as "wait for ALL slots" by setting num_sync to nmembers.
 		 *
 		 * This differs from synchronous_standby_names where "standby1, standby2"
 		 * means "wait for first 1 standby". For synchronized_standby_slots,
 		 * requiring all slots provides safer failover semantics by default.
 		 */
-		if (syncrep_parse_result->num_sync == 1 &&
-			syncrep_parse_result->syncrep_method == SYNC_REP_PRIORITY &&
-			syncrep_parse_result->nmembers > 1)
-		{
-			/*
-			 * Distinguish simple list syntax from explicit priority syntax
-			 * ("FIRST N (...)" or "N (...)"). This prevents a list whose
-			 * first slot name starts with "first" (for example,
-			 * "firstslot, secondslot") from being misclassified.
-			 */
-			if (!IsPrioritySyncStandbySlotsSyntax(*newval))
-				syncrep_parse_result->num_sync = syncrep_parse_result->nmembers;
-		}
+		if (syncrep_parse_result->syncrep_method == SYNC_REP_DEFAULT)
+			syncrep_parse_result->num_sync = syncrep_parse_result->nmembers;
 
 		/* validate every member name as a slot name */
 		mname = syncrep_parse_result->member_names;
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 46a778f0917..34fbac06e48 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -1103,6 +1103,10 @@ check_synchronous_standby_names(char **newval, void **extra, GucSource source)
 			return false;
 		}
 
+		/* Default to FIRST 1 (name ...) priority method if not specified */
+		if (syncrep_parse_result->syncrep_method == SYNC_REP_DEFAULT)
+			syncrep_parse_result->syncrep_method = SYNC_REP_PRIORITY;
+
 		/* GUC extra value must be guc_malloc'd, not palloc'd */
 		pconf = (SyncRepConfigData *)
 			guc_malloc(LOG, syncrep_parse_result->config_size);
diff --git a/src/backend/replication/syncrep_gram.y b/src/backend/replication/syncrep_gram.y
index 1b9d7b2edc4..f1550e109ef 100644
--- a/src/backend/replication/syncrep_gram.y
+++ b/src/backend/replication/syncrep_gram.y
@@ -65,7 +65,7 @@ result:
 	;
 
 standby_config:
-		standby_list				{ $$ = create_syncrep_config("1", $1, SYNC_REP_PRIORITY); }
+		standby_list				{ $$ = create_syncrep_config("1", $1, SYNC_REP_DEFAULT); }
 		| NUM '(' standby_list ')'		{ $$ = create_syncrep_config($1, $3, SYNC_REP_PRIORITY); }
 		| ANY NUM '(' standby_list ')'		{ $$ = create_syncrep_config($2, $4, SYNC_REP_QUORUM); }
 		| FIRST NUM '(' standby_list ')'		{ $$ = create_syncrep_config($2, $4, SYNC_REP_PRIORITY); }
diff --git a/src/include/replication/syncrep.h b/src/include/replication/syncrep.h
index 24e22f9cafa..9554271907d 100644
--- a/src/include/replication/syncrep.h
+++ b/src/include/replication/syncrep.h
@@ -34,6 +34,7 @@
 /* syncrep_method of SyncRepConfigData */
 #define SYNC_REP_PRIORITY		0
 #define SYNC_REP_QUORUM		1
+#define SYNC_REP_DEFAULT	2
 
 /*
  * SyncRepGetCandidateStandbys returns an array of these structs,
-- 
2.43.0