Invalid primary_slot_name triggers warnings in all processes on reload
Hi,
While reviewing the patch at [1]/messages/by-id/CANhcyEUUWJ7XBycqFuqQg=eZ_==KT6S7E+rdMy-GE23oLa8tmQ@mail.gmail.com, I noticed that if primary_slot_name is
set to an invalid slot name in postgresql.conf and the configuration file
is reloaded, all running postgres processes emit the WARNING message
as follows. Isn't this a bug?
To fix this issue, GUC_check_errmsg() should be used instead of ereport()
when an invalid slot name is found in the setting. Thoughts?
------------------------------------
$ initdb -D data
$ pg_ctl -D data start
$ psql <<EOF
ALTER SYSTEM SET log_line_prefix TO '[%b] ';
SELECT pg_reload_conf();
\! echo "primary_slot_name = 'invalid-'" >> data/postgresql.conf
SELECT pg_reload_conf();
EOF
[postmaster] WARNING: replication slot name "invalid-" contains
invalid character
[postmaster] HINT: Replication slot names may only contain lower case
letters, numbers, and the underscore character.
[postmaster] LOG: invalid value for parameter "primary_slot_name": "invalid-"
[postmaster] LOG: configuration file
"/System/Volumes/Data/dav/head-pgsql/data/postgresql.conf" contains
errors; unaffected changes were applied
[logical replication launcher] WARNING: replication slot name
"invalid-" contains invalid character
[logical replication launcher] HINT: Replication slot names may only
contain lower case letters, numbers, and the underscore character.
[checkpointer] WARNING: replication slot name "invalid-" contains
invalid character
[checkpointer] HINT: Replication slot names may only contain lower
case letters, numbers, and the underscore character.
[walwriter] WARNING: replication slot name "invalid-" contains
invalid character
[walwriter] HINT: Replication slot names may only contain lower case
letters, numbers, and the underscore character.
hrk:head-pgsql postgres$ [background writer] WARNING: replication
slot name "invalid-" contains invalid character
[background writer] HINT: Replication slot names may only contain
lower case letters, numbers, and the underscore character.
[io worker] WARNING: replication slot name "invalid-" contains
invalid character
[io worker] HINT: Replication slot names may only contain lower case
letters, numbers, and the underscore character.
[io worker] WARNING: replication slot name "invalid-" contains
invalid character
[io worker] HINT: Replication slot names may only contain lower case
letters, numbers, and the underscore character.
[autovacuum launcher] WARNING: replication slot name "invalid-"
contains invalid character
[autovacuum launcher] HINT: Replication slot names may only contain
lower case letters, numbers, and the underscore character.
[io worker] WARNING: replication slot name "invalid-" contains
invalid character
[io worker] HINT: Replication slot names may only contain lower case
letters, numbers, and the underscore character.
------------------------------------
Regards,
[1]: /messages/by-id/CANhcyEUUWJ7XBycqFuqQg=eZ_==KT6S7E+rdMy-GE23oLa8tmQ@mail.gmail.com
--
Fujii Masao
On Fri, Sep 12, 2025 at 9:12 PM Fujii Masao <masao.fujii@gmail.com> wrote:
Hi,
While reviewing the patch at [1], I noticed that if primary_slot_name is
set to an invalid slot name in postgresql.conf and the configuration file
is reloaded, all running postgres processes emit the WARNING message
as follows. Isn't this a bug?To fix this issue, GUC_check_errmsg() should be used instead of ereport()
when an invalid slot name is found in the setting. Thoughts?
Another simple fix would be to have processes other than the postmaster
report invalid primary_slot_name at DEBUG3 instead of WARNING.
In that case, only the postmaster reports it at WARNING, so by default
only that message appears in the log file. This matches the behavior for
other GUC parameters with invalid settings.
I've attached a patch implementing this approach. Thought?
--
Fujii Masao
Attachments:
v1-0001-Reduce-log-level-for-invalid-primary_slot_name-in.patchapplication/octet-stream; name=v1-0001-Reduce-log-level-for-invalid-primary_slot_name-in.patchDownload
From ddaf1bf2413c6ed394900da53827045a93df8864 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Thu, 18 Sep 2025 19:08:35 +0900
Subject: [PATCH v1] Reduce log level for invalid primary_slot_name in
non-postmaster processes.
Previously, when primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, not only the postmaster but also
other backend processes reported a WARNING. This could result in a flood
of duplicate messages when many processes were running.
This behavior was also inconsistent with other GUC parameters,
where non-postmaster processes log such messages at DEBUG3,
so by default only the postmaster reports the message to the log.
To address this issue, this commit makes primary_slot_name follow
the same convention by reducing the log level in non-postmaster
processes from WARNING to DEBUG3.
---
src/backend/access/transam/xlogrecovery.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 346319338a0..a70a8f1e0fa 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4761,7 +4761,8 @@ bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, false, WARNING))
+ !ReplicationSlotValidateName(*newval, false,
+ IsUnderPostmaster ? DEBUG3 : WARNING))
return false;
return true;
--
2.50.1
On Thu, Sep 18, 2025 at 10:54 PM Fujii Masao <masao.fujii@gmail.com> wrote:
On Fri, Sep 12, 2025 at 9:12 PM Fujii Masao <masao.fujii@gmail.com> wrote:
Hi,
While reviewing the patch at [1], I noticed that if primary_slot_name is
set to an invalid slot name in postgresql.conf and the configuration file
is reloaded, all running postgres processes emit the WARNING message
as follows. Isn't this a bug?To fix this issue, GUC_check_errmsg() should be used instead of ereport()
when an invalid slot name is found in the setting. Thoughts?Another simple fix would be to have processes other than the postmaster
report invalid primary_slot_name at DEBUG3 instead of WARNING.
In that case, only the postmaster reports it at WARNING, so by default
only that message appears in the log file. This matches the behavior for
other GUC parameters with invalid settings.I've attached a patch implementing this approach. Thought?
This approach unexpectedly suppresses the log message about an invalid
primary_slot_name when it's set with ALTER SYSTEM SET, so it isn't suitable.
Instead, we should use GUC_check_xxx() functions for error reporting
in the GUC check hook.
I've attached a patch that updates the check hook for primary_slot_name
to use GUC_check_errdetail() and GUC_check_errhint(). As with other
GUC parameters, this makes non-postmaster processes log at DEBUG3,
so by default only the postmaster's message appears in the log file.
Regards,
--
Fujii Masao
Attachments:
v2-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchapplication/octet-stream; name=v2-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchDownload
From 59aa7374bc95b942e9ba35b87def4e3de5bf1f00 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Thu, 18 Sep 2025 23:50:29 +0900
Subject: [PATCH v2] Make invalid primary_slot_name follow standard GUC error
reporting.
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
---
src/backend/access/transam/xlogrecovery.c | 2 +-
src/backend/replication/slot.c | 66 ++++++++++++++---------
2 files changed, 41 insertions(+), 27 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 346319338a0..c88c3d9edc3 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4761,7 +4761,7 @@ bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, false, WARNING))
+ !ReplicationSlotValidateName(*newval, false, 0))
return false;
return true;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fd0fdb96d42..addd10a0a79 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -258,7 +258,12 @@ ReplicationSlotShmemExit(int code, Datum arg)
}
/*
- * Check whether the passed slot name is valid and report errors at elevel.
+ * Check whether the passed slot name is valid and report errors.
+ *
+ * When called from a GUC check hook, elevel must be 0. In that case,
+ * errors are reported as additional details or hints using
+ * GUC_check_errdetail() and GUC_check_errhint(). Otherwise, elevel
+ * must be nonzero, and errors are reported at that log level with ereport().
*
* An error will be reported for a reserved replication slot name if
* allow_reserved_name is set to false.
@@ -266,30 +271,29 @@ ReplicationSlotShmemExit(int code, Datum arg)
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns whether the slot name is valid or not if elevel < ERROR.
*/
bool
ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
int elevel)
{
const char *cp;
+ int err_code = 0;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
- return false;
+ err_code = ERRCODE_INVALID_NAME;
+ err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ goto error;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
- return false;
+ err_code = ERRCODE_NAME_TOO_LONG;
+ err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ goto error;
}
for (cp = name; *cp; cp++)
@@ -298,28 +302,38 @@ ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
- return false;
+ err_code = ERRCODE_INVALID_NAME;
+ err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
+ goto error;
}
}
if (!allow_reserved_name && IsSlotForConflictCheck(name))
{
- ereport(elevel,
- errcode(ERRCODE_RESERVED_NAME),
- errmsg("replication slot name \"%s\" is reserved",
- name),
- errdetail("The name \"%s\" is reserved for the conflict detection slot.",
- CONFLICT_DETECTION_SLOT));
-
- return false;
+ err_code = ERRCODE_RESERVED_NAME;
+ err_msg = psprintf(_("replication slot name \"%s\" is reserved"), name);
+ err_hint = psprintf(_("The name \"%s\" is reserved for the conflict detection slot."),
+ CONFLICT_DETECTION_SLOT);
+ goto error;
}
return true;
+
+error:
+ if (elevel == 0)
+ {
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
+ }
+ else
+ ereport(elevel,
+ (errcode(err_code),
+ errmsg("%s", err_msg),
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0));
+
+ return false;
}
/*
--
2.50.1
On Sep 19, 2025, at 00:48, Fujii Masao <masao.fujii@gmail.com> wrote:
Fujii Masao
<v2-0001-Make-invalid-primary_slot_name-follow-standard-GU.patch>
```
+error:
+ if (elevel == 0)
+ {
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
+ }
+ else
+ ereport(elevel,
+ (errcode(err_code),
+ errmsg("%s", err_msg),
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0));
+
+ return false;
```
Do we need to free memory pointed by err_msg and err_hint that were allocated from psprintf()? The code comment of psprintf() says caller is responsible for free the memory.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
On Fri, Sep 19, 2025 at 8:21 AM Chao Li <li.evan.chao@gmail.com> wrote:
On Sep 19, 2025, at 00:48, Fujii Masao <masao.fujii@gmail.com> wrote:
Fujii Masao
<v2-0001-Make-invalid-primary_slot_name-follow-standard-GU.patch>``` +error: + if (elevel == 0) + { + GUC_check_errdetail("%s", err_msg); + if (err_hint != NULL) + GUC_check_errhint("%s", err_hint); + } + else + ereport(elevel, + (errcode(err_code), + errmsg("%s", err_msg), + (err_hint != NULL) ? errhint("%s", err_hint) : 0)); + + return false; ```Do we need to free memory pointed by err_msg and err_hint that were allocated from psprintf()? The code comment of psprintf() says caller is responsible for free the memory.
Thanks for the review!
I had thought that since the short-lived "config file processing" memory context
is used when processing the configuration file and then freed afterward,
there was no need for ReplicationSlotValidateName() to call pfree().
However, when it's called by StartupReorderBuffer() with elevel = DEBUG2,
allocations seem to be made in TopMemoryContext, so they could persist
much longer.
So I agree it's safer to free them explicitly. In the attached updated patch,
ReplicationSlotValidateName() now pfrees err_msg and err_hint when needed.
Regards,
--
Fujii Masao
Attachments:
v3-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchapplication/octet-stream; name=v3-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchDownload
From c5aa80b7347406bd68cdc4761997eb699ba214c1 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 19 Sep 2025 15:05:47 +0900
Subject: [PATCH v3] Make invalid primary_slot_name follow standard GUC error
reporting.
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CAHGQGwFud-cvthCTfusBfKHBS6Jj6kdAPTdLWKvP2qjUX6L_wA@mail.gmail.com
---
src/backend/access/transam/xlogrecovery.c | 2 +-
src/backend/replication/slot.c | 70 ++++++++++++++---------
2 files changed, 45 insertions(+), 27 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 346319338a0..c88c3d9edc3 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4761,7 +4761,7 @@ bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, false, WARNING))
+ !ReplicationSlotValidateName(*newval, false, 0))
return false;
return true;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fd0fdb96d42..5e3bc376750 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -258,7 +258,12 @@ ReplicationSlotShmemExit(int code, Datum arg)
}
/*
- * Check whether the passed slot name is valid and report errors at elevel.
+ * Check whether the passed slot name is valid and report errors.
+ *
+ * When called from a GUC check hook, elevel must be 0. In that case,
+ * errors are reported as additional details or hints using
+ * GUC_check_errdetail() and GUC_check_errhint(). Otherwise, elevel
+ * must be nonzero, and errors are reported at that log level with ereport().
*
* An error will be reported for a reserved replication slot name if
* allow_reserved_name is set to false.
@@ -266,30 +271,29 @@ ReplicationSlotShmemExit(int code, Datum arg)
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns whether the slot name is valid or not if elevel < ERROR.
*/
bool
ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
int elevel)
{
const char *cp;
+ int err_code = 0;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
- return false;
+ err_code = ERRCODE_INVALID_NAME;
+ err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ goto error;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
- return false;
+ err_code = ERRCODE_NAME_TOO_LONG;
+ err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ goto error;
}
for (cp = name; *cp; cp++)
@@ -298,28 +302,42 @@ ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
- return false;
+ err_code = ERRCODE_INVALID_NAME;
+ err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
+ goto error;
}
}
if (!allow_reserved_name && IsSlotForConflictCheck(name))
{
- ereport(elevel,
- errcode(ERRCODE_RESERVED_NAME),
- errmsg("replication slot name \"%s\" is reserved",
- name),
- errdetail("The name \"%s\" is reserved for the conflict detection slot.",
- CONFLICT_DETECTION_SLOT));
-
- return false;
+ err_code = ERRCODE_RESERVED_NAME;
+ err_msg = psprintf(_("replication slot name \"%s\" is reserved"), name);
+ err_hint = psprintf(_("The name \"%s\" is reserved for the conflict detection slot."),
+ CONFLICT_DETECTION_SLOT);
+ goto error;
}
return true;
+
+error:
+ if (elevel == 0)
+ {
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
+ }
+ else
+ ereport(elevel,
+ (errcode(err_code),
+ errmsg("%s", err_msg),
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0));
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+
+ return false;
}
/*
--
2.50.1
On Fri, Sep 19, 2025 at 12:00 PM Fujii Masao <masao.fujii@gmail.com> wrote:
So I agree it's safer to free them explicitly. In the attached updated patch,
ReplicationSlotValidateName() now pfrees err_msg and err_hint when needed.
+error:
+ if (elevel == 0)
+ {
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
I see that other places use GUC_check_errcode. See
check_synchronous_standby_names. So, shouldn't we use it here as well?
I don't see any other place distinguishing GUC related errors in this
way. It seems the other way to differentiate throwing errors for GUC
related messages is used in call_string_check_hook and friends. It may
not be used as it is but it can give some ideas to explore. I have not
explored in detail so it may not be relevant here but it is worth
checking once.
--
With Regards,
Amit Kapila.
Hello,
/* - * Check whether the passed slot name is valid and report errors at elevel. + * Check whether the passed slot name is valid and report errors. + * + * When called from a GUC check hook, elevel must be 0. In that case, + * errors are reported as additional details or hints using + * GUC_check_errdetail() and GUC_check_errhint(). Otherwise, elevel + * must be nonzero, and errors are reported at that log level with ereport().
I find this an odd calling convention; I don't think we use it anywhere
else and I wonder if we shouldn't split this up in two functions calling
a third one that returns the string messages, to avoid the oddity. I'd
do something like
bool
ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
char **err_msg, char **err_hint)
and then if this returns false, err_msg and err_hint have been populated
and can be used to throw the error or added as GUC_check_* arguments, or
just ignored.
+error: + if (elevel == 0) + { + GUC_check_errdetail("%s", err_msg); + if (err_hint != NULL) + GUC_check_errhint("%s", err_hint); + } + else + ereport(elevel, + (errcode(err_code), + errmsg("%s", err_msg), + (err_hint != NULL) ? errhint("%s", err_hint) : 0));
Please note that since you're using already translated strings as
arguments, you must use errmsg_internal() and errhint_internal(), to
avoid doubly-translating these messages.
+ pfree(err_msg); + if (err_hint != NULL) + pfree(err_hint); + + return false;
Not sure how valuable pfree'ing these really is ... I suspect this is
only called in contexts that aren't sensitive to a few bytes of
transient leakage.
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"El hombre nunca sabe de lo que es capaz hasta que lo intenta" (C. Dickens)
On Wed, Sep 24, 2025 at 12:11 AM Álvaro Herrera <alvherre@kurilemu.de> wrote:
Hello,
/* - * Check whether the passed slot name is valid and report errors at elevel. + * Check whether the passed slot name is valid and report errors. + * + * When called from a GUC check hook, elevel must be 0. In that case, + * errors are reported as additional details or hints using + * GUC_check_errdetail() and GUC_check_errhint(). Otherwise, elevel + * must be nonzero, and errors are reported at that log level with ereport().I find this an odd calling convention; I don't think we use it anywhere
else and I wonder if we shouldn't split this up in two functions calling
a third one that returns the string messages, to avoid the oddity. I'd
do something likebool
ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
char **err_msg, char **err_hint)and then if this returns false, err_msg and err_hint have been populated
and can be used to throw the error or added as GUC_check_* arguments, or
just ignored.
Yeah, that's an option. If we go with it, we'd probably need to return
the error code as well. Since it looks better, I'll consider updating
the patch accordingly.
BTW, about validating primary_slot_name in the check hook: I'm not sure
it's really worthwhile. Even without this validation, an invalid slot name
will raise an error anyway, and this validation can't catch all invalid cases.
So its value seems limited. If the check hook doesn't need to do
this validation, then ReplicationSlotValidateName() doesn't need to be
updated for that purpose.
+error: + if (elevel == 0) + { + GUC_check_errdetail("%s", err_msg); + if (err_hint != NULL) + GUC_check_errhint("%s", err_hint); + } + else + ereport(elevel, + (errcode(err_code), + errmsg("%s", err_msg), + (err_hint != NULL) ? errhint("%s", err_hint) : 0));Please note that since you're using already translated strings as
arguments, you must use errmsg_internal() and errhint_internal(), to
avoid doubly-translating these messages.
Right, thanks for pointing that out.
+ pfree(err_msg); + if (err_hint != NULL) + pfree(err_hint); + + return false;Not sure how valuable pfree'ing these really is ... I suspect this is
only called in contexts that aren't sensitive to a few bytes of
transient leakage.
I initially thought the same since the short-lived config file processing
context is used when handling the config file.
But when ReplicationSlotValidateName() is called outside the GUC check hook,
seems allocations can end up in TopMemoryContext. Even though the memory use
is small, it's safer to call pfree(), and the overhead is negligible.
So I didn't see a strong reason to skip it....
Regards,
--
Fujii Masao
On Wed, Sep 24, 2025 at 11:57 PM Fujii Masao <masao.fujii@gmail.com> wrote:
On Wed, Sep 24, 2025 at 12:11 AM Álvaro Herrera <alvherre@kurilemu.de> wrote:
Hello,
/* - * Check whether the passed slot name is valid and report errors at elevel. + * Check whether the passed slot name is valid and report errors. + * + * When called from a GUC check hook, elevel must be 0. In that case, + * errors are reported as additional details or hints using + * GUC_check_errdetail() and GUC_check_errhint(). Otherwise, elevel + * must be nonzero, and errors are reported at that log level with ereport().I find this an odd calling convention; I don't think we use it anywhere
else and I wonder if we shouldn't split this up in two functions calling
a third one that returns the string messages, to avoid the oddity. I'd
do something likebool
ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
char **err_msg, char **err_hint)and then if this returns false, err_msg and err_hint have been populated
and can be used to throw the error or added as GUC_check_* arguments, or
just ignored.Yeah, that's an option. If we go with it, we'd probably need to return
the error code as well. Since it looks better, I'll consider updating
the patch accordingly.
Are you still planning to work on it? I am asking because it is better
to fix this before merging the change for another somewhat related
bug-fix patch being discussed in thread [1]/messages/by-id/CAA5-nLB0JhVO-ykpfguxyGkoAk1tECi5xMTqAoVJ6spnDiQzaw@mail.gmail.com.
BTW, about validating primary_slot_name in the check hook: I'm not sure
it's really worthwhile. Even without this validation, an invalid slot name
will raise an error anyway, and this validation can't catch all invalid cases.
So its value seems limited. If the check hook doesn't need to do
this validation, then ReplicationSlotValidateName() doesn't need to be
updated for that purpose.
Right. I think we can consider this separately just as a HEAD patch.
[1]: /messages/by-id/CAA5-nLB0JhVO-ykpfguxyGkoAk1tECi5xMTqAoVJ6spnDiQzaw@mail.gmail.com
--
With Regards,
Amit Kapila.
On Thu, Oct 9, 2025 at 2:06 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Are you still planning to work on it?
Yes, thanks for the ping!
Attached is the updated version of the patch.
Please note that since you're using already translated strings as
arguments, you must use errmsg_internal() and errhint_internal(), to
avoid doubly-translating these messages.
I've updated the patch to use errmsg_internal() and errhint_internal().
However, for GUCs, GUC_check_errdetail() and GUC_check_errhint() are
still used - and since they also translate their input, we might need
something like GUC_check_errdetail_internal() and
GUC_check_errhint_internal() to avoid double translation. Thought?
I haven't added those functions in the attached patch yet.
This isn't directly related to this thread, but while updating the patch,
I noticed that the call_xxx_check_hook() functions in guc.c use errhint()
instead of errhint_internal(). That might also cause double translation.
Interestingly, for log and detail messages they already use errmsg_internal()
and errdetail_internal(). Only the HINT messages still use the non-internal
version. Should we switch to use errhint_internal() as well?
I am asking because it is better
to fix this before merging the change for another somewhat related
bug-fix patch being discussed in thread [1].
+1
BTW, about validating primary_slot_name in the check hook: I'm not sure
it's really worthwhile. Even without this validation, an invalid slot name
will raise an error anyway, and this validation can't catch all invalid cases.
So its value seems limited. If the check hook doesn't need to do
this validation, then ReplicationSlotValidateName() doesn't need to be
updated for that purpose.Right. I think we can consider this separately just as a HEAD patch.
+1
Regards,
--
Fujii Masao
Attachments:
v4-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchapplication/octet-stream; name=v4-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchDownload
From 91be58df0eb116300e8f29f713ade4a9a7754274 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 3 Oct 2025 23:00:57 +0900
Subject: [PATCH v4] Make invalid primary_slot_name follow standard GUC error
reporting.
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
---
src/backend/access/transam/xlogrecovery.c | 13 +++-
src/backend/replication/slot.c | 72 +++++++++++++++--------
src/include/replication/slot.h | 3 +
3 files changed, 64 insertions(+), 24 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..3e3c4da01a2 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4761,9 +4761,20 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue
bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, false, WARNING))
+ !ReplicationSlotValidateNameInternal(*newval, false, &err_code,
+ &err_msg, &err_hint))
+ {
+ GUC_check_errcode(err_code);
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
return false;
+ }
return true;
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fd0fdb96d42..9e92a79f993 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -260,35 +260,66 @@ ReplicationSlotShmemExit(int code, Datum arg)
/*
* Check whether the passed slot name is valid and report errors at elevel.
*
+ * See comments for ReplicationSlotValidateNameInternal().
+ */
+bool
+ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
+ int elevel)
+{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
+ if (!ReplicationSlotValidateNameInternal(name, allow_reserved_name,
+ &err_code, &err_msg, &err_hint))
+ {
+ ereport(elevel,
+ errcode(err_code),
+ errmsg_internal("%s", err_msg),
+ (err_hint != NULL) ? errhint_internal("%s", err_hint) : 0);
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Check whether the passed slot name is valid.
+ *
* An error will be reported for a reserved replication slot name if
* allow_reserved_name is set to false.
*
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns true if the slot name is valid. Otherwise, returns false and stores
+ * the error code, error message, and optional hint in err_code, err_msg, and
+ * err_hint, respectively. The caller is responsible for freeing err_msg and
+ * err_hint, which are palloc'd.
*/
bool
-ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
- int elevel)
+ReplicationSlotValidateNameInternal(const char *name, bool allow_reserved_name,
+ int *err_code, char **err_msg, char **err_hint)
{
const char *cp;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ *err_hint = NULL;
return false;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
+ *err_code = ERRCODE_NAME_TOO_LONG;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ *err_hint = NULL;
return false;
}
@@ -298,24 +329,19 @@ ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ *err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
return false;
}
}
if (!allow_reserved_name && IsSlotForConflictCheck(name))
{
- ereport(elevel,
- errcode(ERRCODE_RESERVED_NAME),
- errmsg("replication slot name \"%s\" is reserved",
- name),
- errdetail("The name \"%s\" is reserved for the conflict detection slot.",
- CONFLICT_DETECTION_SLOT));
-
+ *err_code = ERRCODE_RESERVED_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is reserved"), name);
+ *err_hint = psprintf(_("The name \"%s\" is reserved for the conflict detection slot."),
+ CONFLICT_DETECTION_SLOT);
return false;
}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index fe62162cde3..09c69f83d57 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -321,6 +321,9 @@ extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name,
bool allow_reserved_name,
int elevel);
+extern bool ReplicationSlotValidateNameInternal(const char *name,
+ bool allow_reserved_name,
+ int *err_code, char **err_msg, char **err_hint);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.50.1
On Thu, Oct 9, 2025 at 2:26 PM Fujii Masao <masao.fujii@gmail.com> wrote:
Please note that since you're using already translated strings as
arguments, you must use errmsg_internal() and errhint_internal(), to
avoid doubly-translating these messages.I've updated the patch to use errmsg_internal() and errhint_internal().
However, for GUCs, GUC_check_errdetail() and GUC_check_errhint() are
still used - and since they also translate their input, we might need
something like GUC_check_errdetail_internal() and
GUC_check_errhint_internal() to avoid double translation. Thought?
I haven't added those functions in the attached patch yet.
OK, I've implemented this and attached two patches.
0001 fixes the issue we've been discussing. It's mostly the same as
the previous version I posted.
0002 adds GUC_check_errmsg_internal(), GUC_check_errdetail_internal(),
and GUC_check_errhint_internal(). These work like their counterparts
(e.g., GUC_check_errmsg()) but skip translation. It also updates
the check hook for primary_slot_name to use these new functions
when handling already-translated error messages.
Regards,
--
Fujii Masao
Attachments:
v5-0002-Avoid-double-translation-of-messages-for-invalid-.patchapplication/octet-stream; name=v5-0002-Avoid-double-translation-of-messages-for-invalid-.patchDownload
From 967a540348b4e92608c24125b4bd840042eb80ec Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 10 Oct 2025 14:16:56 +0900
Subject: [PATCH v5 2/2] Avoid double translation of messages for invalid
primary_slot_name.
When primary_slot_name is invalid, ReplicationSlotValidateNameInternal()
returns already translated error messages (when NLS is enabled). Previously,
the check hook for primary_slot_name passed these messages to
GUC_check_errdetail() and GUC_check_errhint(), which could retranslate
them and cause double translation.
To fix this, this commit adds GUC_check_errmsg_internal(),
GUC_check_errdetail_internal(), and GUC_check_errhint_internal().
These work like their counterparts but skip translation. The check hook
for primary_slot_name now uses these new functions when handling
messages from ReplicationSlotValidateNameInternal().
---
src/backend/access/transam/xlogrecovery.c | 10 ++++++--
src/backend/utils/error/elog.c | 31 +++++++++++++++++++++++
src/include/utils/elog.h | 1 +
src/include/utils/guc.h | 18 +++++++++++++
4 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..51374194130 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4769,10 +4769,16 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
!ReplicationSlotValidateNameInternal(*newval, false, &err_code,
&err_msg, &err_hint))
{
+ /*
+ * Use GUC_check_errdetail_internal() and GUC_check_errhint_internal()
+ * instead of GUC_check_errdetail() and GUC_check_errhint(), since the
+ * messages from ReplicationSlotValidateNameInternal() are already
+ * translated. This avoids double translation.
+ */
GUC_check_errcode(err_code);
- GUC_check_errdetail("%s", err_msg);
+ GUC_check_errdetail_internal("%s", err_msg);
if (err_hint != NULL)
- GUC_check_errhint("%s", err_hint);
+ GUC_check_errhint_internal("%s", err_hint);
return false;
}
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 648d2d2e70c..33b4d4d70cc 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -1692,6 +1692,37 @@ format_elog_string(const char *fmt,...)
return edata->message;
}
+/*
+ * This is exactly like format_elog_string() except that strings passed to
+ * format_elog_string_internal are not translated, and are customarily left
+ * out of the internationalization message dictionary. This should be used
+ * when the passed strings have already been translated.
+ */
+char *
+format_elog_string_internal(const char *fmt,...)
+{
+ ErrorData errdata;
+ ErrorData *edata;
+ MemoryContext oldcontext;
+
+ /* Initialize a mostly-dummy error frame */
+ edata = &errdata;
+ MemSet(edata, 0, sizeof(ErrorData));
+ /* the default text domain is the backend's */
+ edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres");
+ /* set the errno to be used to interpret %m */
+ edata->saved_errno = save_format_errnumber;
+
+ oldcontext = MemoryContextSwitchTo(ErrorContext);
+
+ edata->message_id = fmt;
+ EVALUATE_MESSAGE(edata->domain, message, false, false);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return edata->message;
+}
+
/*
* Actual output of the top-of-stack error message
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 348dafbf906..57a57d2b027 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -302,6 +302,7 @@ extern void errsave_finish(struct Node *context,
extern void pre_format_elog_string(int errnumber, const char *domain);
extern char *format_elog_string(const char *fmt,...) pg_attribute_printf(1, 2);
+extern char *format_elog_string_internal(const char *fmt,...) pg_attribute_printf(1, 2);
/* Support for attaching context information to error reports */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..af9f64c93ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -510,4 +510,22 @@ extern void GUC_check_errcode(int sqlerrcode);
pre_format_elog_string(errno, TEXTDOMAIN), \
GUC_check_errhint_string = format_elog_string
+/*
+ * These are exactly like GUC_check_errmsg/errdtail/errhint except that
+ * strings passed to them are not translated, and are customarily left
+ * out of the internationalization message dictionary. This should be used
+ * when the passed strings have already been translated.
+ */
+#define GUC_check_errmsg_internal \
+ pre_format_elog_string(errno, TEXTDOMAIN), \
+ GUC_check_errmsg_string = format_elog_string_internal
+
+#define GUC_check_errdetail_internal \
+ pre_format_elog_string(errno, TEXTDOMAIN), \
+ GUC_check_errdetail_string = format_elog_string_internal
+
+#define GUC_check_errhint_internal \
+ pre_format_elog_string(errno, TEXTDOMAIN), \
+ GUC_check_errhint_string = format_elog_string_internal
+
#endif /* GUC_H */
--
2.50.1
v5-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchapplication/octet-stream; name=v5-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchDownload
From cc5eace1069fa5decdfa8e8fcd2fa2d562b76cea Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 10 Oct 2025 14:01:44 +0900
Subject: [PATCH v5 1/2] Make invalid primary_slot_name follow standard GUC
error reporting.
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
---
src/backend/access/transam/xlogrecovery.c | 13 +++-
src/backend/replication/slot.c | 78 ++++++++++++++++-------
src/include/replication/slot.h | 3 +
3 files changed, 70 insertions(+), 24 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..3e3c4da01a2 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4761,9 +4761,20 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue
bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, false, WARNING))
+ !ReplicationSlotValidateNameInternal(*newval, false, &err_code,
+ &err_msg, &err_hint))
+ {
+ GUC_check_errcode(err_code);
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
return false;
+ }
return true;
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index ac188bb2f77..a4ca363f20d 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -260,35 +260,72 @@ ReplicationSlotShmemExit(int code, Datum arg)
/*
* Check whether the passed slot name is valid and report errors at elevel.
*
+ * See comments for ReplicationSlotValidateNameInternal().
+ */
+bool
+ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
+ int elevel)
+{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
+ if (!ReplicationSlotValidateNameInternal(name, allow_reserved_name,
+ &err_code, &err_msg, &err_hint))
+ {
+ /*
+ * Use errmsg_internal() and errhint_internal() instead of errmsg()
+ * and errhint(), since the messages from
+ * ReplicationSlotValidateNameInternal() are already translated. This
+ * avoids double translation.
+ */
+ ereport(elevel,
+ errcode(err_code),
+ errmsg_internal("%s", err_msg),
+ (err_hint != NULL) ? errhint_internal("%s", err_hint) : 0);
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Check whether the passed slot name is valid.
+ *
* An error will be reported for a reserved replication slot name if
* allow_reserved_name is set to false.
*
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns true if the slot name is valid. Otherwise, returns false and stores
+ * the error code, error message, and optional hint in err_code, err_msg, and
+ * err_hint, respectively. The caller is responsible for freeing err_msg and
+ * err_hint, which are palloc'd.
*/
bool
-ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
- int elevel)
+ReplicationSlotValidateNameInternal(const char *name, bool allow_reserved_name,
+ int *err_code, char **err_msg, char **err_hint)
{
const char *cp;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ *err_hint = NULL;
return false;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
+ *err_code = ERRCODE_NAME_TOO_LONG;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ *err_hint = NULL;
return false;
}
@@ -298,24 +335,19 @@ ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ *err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
return false;
}
}
if (!allow_reserved_name && IsSlotForConflictCheck(name))
{
- ereport(elevel,
- errcode(ERRCODE_RESERVED_NAME),
- errmsg("replication slot name \"%s\" is reserved",
- name),
- errdetail("The name \"%s\" is reserved for the conflict detection slot.",
- CONFLICT_DETECTION_SLOT));
-
+ *err_code = ERRCODE_RESERVED_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is reserved"), name);
+ *err_hint = psprintf(_("The name \"%s\" is reserved for the conflict detection slot."),
+ CONFLICT_DETECTION_SLOT);
return false;
}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index fe62162cde3..09c69f83d57 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -321,6 +321,9 @@ extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name,
bool allow_reserved_name,
int elevel);
+extern bool ReplicationSlotValidateNameInternal(const char *name,
+ bool allow_reserved_name,
+ int *err_code, char **err_msg, char **err_hint);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.50.1
On Fri, Oct 10, 2025 at 2:52 PM Fujii Masao <masao.fujii@gmail.com> wrote:
On Thu, Oct 9, 2025 at 2:26 PM Fujii Masao <masao.fujii@gmail.com> wrote:
Please note that since you're using already translated strings as
arguments, you must use errmsg_internal() and errhint_internal(), to
avoid doubly-translating these messages.I've updated the patch to use errmsg_internal() and errhint_internal().
However, for GUCs, GUC_check_errdetail() and GUC_check_errhint() are
still used - and since they also translate their input, we might need
something like GUC_check_errdetail_internal() and
GUC_check_errhint_internal() to avoid double translation. Thought?
I haven't added those functions in the attached patch yet.OK, I've implemented this and attached two patches.
0001 fixes the issue we've been discussing. It's mostly the same as
the previous version I posted.
No comments on the latest patches — maybe that’s a good
sign of their quality? ;)
Anyway, unless there are any objections, I plan to commit at least
the 0001 patch and backpatch it to all supported branches. I've
attached the patches for the back branches for reference.
Regarding the backpatch: in v17 and earlier, since errhint_internal()
doesn't exist, I used errhint() instead. That means the hint message
might be translated twice, but I think that's minor and acceptable.
Or do you think we should instead backpatch errhint_internal() to
those older branches to avoid the double translation?
Regards,
--
Fujii Masao
Attachments:
v6-0001-PG16-PG17-Make-invalid-primary_slot_name-follow-standard-GU.txttext/plain; charset=UTF-8; name=v6-0001-PG16-PG17-Make-invalid-primary_slot_name-follow-standard-GU.txtDownload
From 77bf7e4d0aff6309037a5c97d1f47078193fdb6f Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 21 Oct 2025 14:20:02 +0900
Subject: [PATCH v6] Make invalid primary_slot_name follow standard GUC error
reporting.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
Backpatch to all supported versions.
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Chao Li <lic@highgo.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/CAHGQGwFud-cvthCTfusBfKHBS6Jj6kdAPTdLWKvP2qjUX6L_wA@mail.gmail.com
Backpatch-through: 13
---
src/backend/access/transam/xlogrecovery.c | 13 ++++-
src/backend/replication/slot.c | 59 +++++++++++++++++------
src/include/replication/slot.h | 2 +
3 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 1917cd4f449..f0fcb9fb881 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4740,9 +4740,20 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue
bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, WARNING))
+ !ReplicationSlotValidateNameInternal(*newval, &err_code,
+ &err_msg, &err_hint))
+ {
+ GUC_check_errcode(err_code);
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
return false;
+ }
return true;
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 94993c677d2..99fe464b99c 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -243,31 +243,62 @@ ReplicationSlotShmemExit(int code, Datum arg)
/*
* Check whether the passed slot name is valid and report errors at elevel.
*
+ * See comments for ReplicationSlotValidateNameInternal().
+ */
+bool
+ReplicationSlotValidateName(const char *name, int elevel)
+{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
+ if (!ReplicationSlotValidateNameInternal(name,
+ &err_code, &err_msg, &err_hint))
+ {
+ ereport(elevel,
+ errcode(err_code),
+ errmsg_internal("%s", err_msg),
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0);
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Check whether the passed slot name is valid.
+ *
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns true if the slot name is valid. Otherwise, returns false and stores
+ * the error code, error message, and optional hint in err_code, err_msg, and
+ * err_hint, respectively. The caller is responsible for freeing err_msg and
+ * err_hint, which are palloc'd.
*/
bool
-ReplicationSlotValidateName(const char *name, int elevel)
+ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint)
{
const char *cp;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ *err_hint = NULL;
return false;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
+ *err_code = ERRCODE_NAME_TOO_LONG;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ *err_hint = NULL;
return false;
}
@@ -277,11 +308,9 @@ ReplicationSlotValidateName(const char *name, int elevel)
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ *err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
return false;
}
}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 07561bc4742..43594bb9bef 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -258,6 +258,8 @@ extern void ReplicationSlotMarkDirty(void);
/* misc stuff */
extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name, int elevel);
+extern bool ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.50.1
v6-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchapplication/octet-stream; name=v6-0001-Make-invalid-primary_slot_name-follow-standard-GU.patchDownload
From 4b7349f77f9f5b4d98323e90d583d7e0c4ce7332 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 21 Oct 2025 13:55:23 +0900
Subject: [PATCH v6] Make invalid primary_slot_name follow standard GUC error
reporting.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
Backpatch to all supported versions.
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Chao Li <lic@highgo.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/CAHGQGwFud-cvthCTfusBfKHBS6Jj6kdAPTdLWKvP2qjUX6L_wA@mail.gmail.com
Backpatch-through: 13
---
src/backend/access/transam/xlogrecovery.c | 13 +++-
src/backend/replication/slot.c | 78 ++++++++++++++++-------
src/include/replication/slot.h | 3 +
3 files changed, 70 insertions(+), 24 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 52ff4d119e6..3e3c4da01a2 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4761,9 +4761,20 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue
bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, false, WARNING))
+ !ReplicationSlotValidateNameInternal(*newval, false, &err_code,
+ &err_msg, &err_hint))
+ {
+ GUC_check_errcode(err_code);
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
return false;
+ }
return true;
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index ac188bb2f77..a4ca363f20d 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -260,35 +260,72 @@ ReplicationSlotShmemExit(int code, Datum arg)
/*
* Check whether the passed slot name is valid and report errors at elevel.
*
+ * See comments for ReplicationSlotValidateNameInternal().
+ */
+bool
+ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
+ int elevel)
+{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
+ if (!ReplicationSlotValidateNameInternal(name, allow_reserved_name,
+ &err_code, &err_msg, &err_hint))
+ {
+ /*
+ * Use errmsg_internal() and errhint_internal() instead of errmsg()
+ * and errhint(), since the messages from
+ * ReplicationSlotValidateNameInternal() are already translated. This
+ * avoids double translation.
+ */
+ ereport(elevel,
+ errcode(err_code),
+ errmsg_internal("%s", err_msg),
+ (err_hint != NULL) ? errhint_internal("%s", err_hint) : 0);
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Check whether the passed slot name is valid.
+ *
* An error will be reported for a reserved replication slot name if
* allow_reserved_name is set to false.
*
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns true if the slot name is valid. Otherwise, returns false and stores
+ * the error code, error message, and optional hint in err_code, err_msg, and
+ * err_hint, respectively. The caller is responsible for freeing err_msg and
+ * err_hint, which are palloc'd.
*/
bool
-ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
- int elevel)
+ReplicationSlotValidateNameInternal(const char *name, bool allow_reserved_name,
+ int *err_code, char **err_msg, char **err_hint)
{
const char *cp;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ *err_hint = NULL;
return false;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
+ *err_code = ERRCODE_NAME_TOO_LONG;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ *err_hint = NULL;
return false;
}
@@ -298,24 +335,19 @@ ReplicationSlotValidateName(const char *name, bool allow_reserved_name,
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ *err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
return false;
}
}
if (!allow_reserved_name && IsSlotForConflictCheck(name))
{
- ereport(elevel,
- errcode(ERRCODE_RESERVED_NAME),
- errmsg("replication slot name \"%s\" is reserved",
- name),
- errdetail("The name \"%s\" is reserved for the conflict detection slot.",
- CONFLICT_DETECTION_SLOT));
-
+ *err_code = ERRCODE_RESERVED_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is reserved"), name);
+ *err_hint = psprintf(_("The name \"%s\" is reserved for the conflict detection slot."),
+ CONFLICT_DETECTION_SLOT);
return false;
}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index fe62162cde3..09c69f83d57 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -321,6 +321,9 @@ extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name,
bool allow_reserved_name,
int elevel);
+extern bool ReplicationSlotValidateNameInternal(const char *name,
+ bool allow_reserved_name,
+ int *err_code, char **err_msg, char **err_hint);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.50.1
v6-0001-PG13-PG15-Make-invalid-primary_slot_name-follow-standard-GU.txttext/plain; charset=UTF-8; name=v6-0001-PG13-PG15-Make-invalid-primary_slot_name-follow-standard-GU.txtDownload
From 15a191136ffd744b970678670454a9d34c73ed80 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 21 Oct 2025 14:31:48 +0900
Subject: [PATCH v6] Make invalid primary_slot_name follow standard GUC error
reporting.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
Backpatch to all supported versions.
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Chao Li <lic@highgo.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/CAHGQGwFud-cvthCTfusBfKHBS6Jj6kdAPTdLWKvP2qjUX6L_wA@mail.gmail.com
Backpatch-through: 13
---
src/backend/replication/slot.c | 59 +++++++++++++++++++++++++---------
src/backend/utils/misc/guc.c | 13 +++++++-
src/include/replication/slot.h | 2 ++
3 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c59f249fb61..0d16c791116 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -188,31 +188,62 @@ ReplicationSlotShmemExit(int code, Datum arg)
/*
* Check whether the passed slot name is valid and report errors at elevel.
*
+ * See comments for ReplicationSlotValidateNameInternal().
+ */
+bool
+ReplicationSlotValidateName(const char *name, int elevel)
+{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
+ if (!ReplicationSlotValidateNameInternal(name,
+ &err_code, &err_msg, &err_hint))
+ {
+ ereport(elevel,
+ errcode(err_code),
+ errmsg_internal("%s", err_msg),
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0);
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Check whether the passed slot name is valid.
+ *
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns true if the slot name is valid. Otherwise, returns false and stores
+ * the error code, error message, and optional hint in err_code, err_msg, and
+ * err_hint, respectively. The caller is responsible for freeing err_msg and
+ * err_hint, which are palloc'd.
*/
bool
-ReplicationSlotValidateName(const char *name, int elevel)
+ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint)
{
const char *cp;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ *err_hint = NULL;
return false;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
+ *err_code = ERRCODE_NAME_TOO_LONG;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ *err_hint = NULL;
return false;
}
@@ -222,11 +253,9 @@ ReplicationSlotValidateName(const char *name, int elevel)
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ *err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
return false;
}
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index abfc581171a..a7f5ad95619 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -13044,9 +13044,20 @@ assign_recovery_target_lsn(const char *newval, void *extra)
static bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, WARNING))
+ !ReplicationSlotValidateNameInternal(*newval, &err_code,
+ &err_msg, &err_hint))
+ {
+ GUC_check_errcode(err_code);
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
return false;
+ }
return true;
}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index deba2c4e499..51dfb740cfa 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -208,6 +208,8 @@ extern void ReplicationSlotMarkDirty(void);
/* misc stuff */
extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name, int elevel);
+extern bool ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.50.1
v6-0001-PG18-Make-invalid-primary_slot_name-follow-standard-GU.txttext/plain; charset=UTF-8; name=v6-0001-PG18-Make-invalid-primary_slot_name-follow-standard-GU.txtDownload
From 31e9f754f04c0fc76f87ba44bad045a56ab10f93 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 21 Oct 2025 14:08:56 +0900
Subject: [PATCH v6] Make invalid primary_slot_name follow standard GUC error
reporting.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Previously, if primary_slot_name was set to an invalid slot name and
the configuration file was reloaded, both the postmaster and all other
backend processes reported a WARNING. With many processes running,
this could produce a flood of duplicate messages. The problem was that
the GUC check hook for primary_slot_name reported errors at WARNING
level via ereport().
This commit changes the check hook to use GUC_check_errdetail() and
GUC_check_errhint() for error reporting. As with other GUC parameters,
this causes non-postmaster processes to log the message at DEBUG3,
so by default, only the postmaster's message appears in the log file.
Backpatch to all supported versions.
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Chao Li <lic@highgo.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/CAHGQGwFud-cvthCTfusBfKHBS6Jj6kdAPTdLWKvP2qjUX6L_wA@mail.gmail.com
Backpatch-through: 13
---
src/backend/access/transam/xlogrecovery.c | 13 ++++-
src/backend/replication/slot.c | 65 +++++++++++++++++------
src/include/replication/slot.h | 2 +
3 files changed, 64 insertions(+), 16 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index efbe77a5747..d5d7afb7206 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4760,9 +4760,20 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue
bool
check_primary_slot_name(char **newval, void **extra, GucSource source)
{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
if (*newval && strcmp(*newval, "") != 0 &&
- !ReplicationSlotValidateName(*newval, WARNING))
+ !ReplicationSlotValidateNameInternal(*newval, &err_code,
+ &err_msg, &err_hint))
+ {
+ GUC_check_errcode(err_code);
+ GUC_check_errdetail("%s", err_msg);
+ if (err_hint != NULL)
+ GUC_check_errhint("%s", err_hint);
return false;
+ }
return true;
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 23c616b1d7d..bb3a5ad074a 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -258,31 +258,68 @@ ReplicationSlotShmemExit(int code, Datum arg)
/*
* Check whether the passed slot name is valid and report errors at elevel.
*
+ * See comments for ReplicationSlotValidateNameInternal().
+ */
+bool
+ReplicationSlotValidateName(const char *name, int elevel)
+{
+ int err_code;
+ char *err_msg = NULL;
+ char *err_hint = NULL;
+
+ if (!ReplicationSlotValidateNameInternal(name,
+ &err_code, &err_msg, &err_hint))
+ {
+ /*
+ * Use errmsg_internal() and errhint_internal() instead of errmsg()
+ * and errhint(), since the messages from
+ * ReplicationSlotValidateNameInternal() are already translated. This
+ * avoids double translation.
+ */
+ ereport(elevel,
+ errcode(err_code),
+ errmsg_internal("%s", err_msg),
+ (err_hint != NULL) ? errhint_internal("%s", err_hint) : 0);
+
+ pfree(err_msg);
+ if (err_hint != NULL)
+ pfree(err_hint);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Check whether the passed slot name is valid.
+ *
* Slot names may consist out of [a-z0-9_]{1,NAMEDATALEN-1} which should allow
* the name to be used as a directory name on every supported OS.
*
- * Returns whether the directory name is valid or not if elevel < ERROR.
+ * Returns true if the slot name is valid. Otherwise, returns false and stores
+ * the error code, error message, and optional hint in err_code, err_msg, and
+ * err_hint, respectively. The caller is responsible for freeing err_msg and
+ * err_hint, which are palloc'd.
*/
bool
-ReplicationSlotValidateName(const char *name, int elevel)
+ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint)
{
const char *cp;
if (strlen(name) == 0)
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" is too short",
- name)));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too short"), name);
+ *err_hint = NULL;
return false;
}
if (strlen(name) >= NAMEDATALEN)
{
- ereport(elevel,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("replication slot name \"%s\" is too long",
- name)));
+ *err_code = ERRCODE_NAME_TOO_LONG;
+ *err_msg = psprintf(_("replication slot name \"%s\" is too long"), name);
+ *err_hint = NULL;
return false;
}
@@ -292,11 +329,9 @@ ReplicationSlotValidateName(const char *name, int elevel)
|| (*cp >= '0' && *cp <= '9')
|| (*cp == '_')))
{
- ereport(elevel,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("replication slot name \"%s\" contains invalid character",
- name),
- errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
+ *err_code = ERRCODE_INVALID_NAME;
+ *err_msg = psprintf(_("replication slot name \"%s\" contains invalid character"), name);
+ *err_hint = psprintf(_("Replication slot names may only contain lower case letters, numbers, and the underscore character."));
return false;
}
}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 76aeeb92242..d9aab934048 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -293,6 +293,8 @@ extern void ReplicationSlotMarkDirty(void);
/* misc stuff */
extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name, int elevel);
+extern bool ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.50.1
Dear Fujii-san,
No comments on the latest patches — maybe that’s a good
sign of their quality? ;)Anyway, unless there are any objections, I plan to commit at least
the 0001 patch and backpatch it to all supported branches. I've
attached the patches for the back branches for reference.
FYI, the patch could not be applied cleanly for PG13 and 14 for my env:
```
postgres (REL_13_STABLE $%=)$ git am ../patches/primary_slot_name/v6-0001-PG13-PG15-Make-invalid-primary_slot_name-follow-standard-GU.txt
Applying: Make invalid primary_slot_name follow standard GUC error reporting.
error: patch failed: src/include/replication/slot.h:208
error: src/include/replication/slot.h: patch does not apply
...
```
Cosmetic comments:
```
+ if (!ReplicationSlotValidateNameInternal(name,
+ &err_code, &err_msg, &err_hint))
...
-ReplicationSlotValidateName(const char *name, int elevel)
+ReplicationSlotValidateNameInternal(const char *name,
+ int *err_code, char **err_msg, char **err_hint)
```
Patches for older branches have strange indent, maybe because
"bool allow_reserved_name" is just removed. Should we move up arguments?
Regarding the backpatch: in v17 and earlier, since errhint_internal()
doesn't exist, I used errhint() instead. That means the hint message
might be translated twice, but I think that's minor and acceptable.
Or do you think we should instead backpatch errhint_internal() to
those older branches to avoid the double translation?
Personally considered it can be added...
Best regards,
Hayato Kuroda
FUJITSU LIMITED
On Tue, Oct 21, 2025 at 5:00 PM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
Dear Fujii-san,
Thanks for testing and reviewing!
No comments on the latest patches — maybe that’s a good
sign of their quality? ;)Anyway, unless there are any objections, I plan to commit at least
the 0001 patch and backpatch it to all supported branches. I've
attached the patches for the back branches for reference.FYI, the patch could not be applied cleanly for PG13 and 14 for my env:
I first applied the patch to v15, then used git cherry-pick to backpatch it
to v14 and v13 without any issues. You can probably do the same to apply it
to those branches.
Cosmetic comments:
``` + if (!ReplicationSlotValidateNameInternal(name, + &err_code, &err_msg, &err_hint)) ... -ReplicationSlotValidateName(const char *name, int elevel) +ReplicationSlotValidateNameInternal(const char *name, + int *err_code, char **err_msg, char **err_hint) ```Patches for older branches have strange indent, maybe because
"bool allow_reserved_name" is just removed. Should we move up arguments?
Since pgindent doesn't treat the current indentation as an issue,
I'm fine keeping it as is, though I don't mind changing it if you think
it's worth updating.
Regarding the backpatch: in v17 and earlier, since errhint_internal()
doesn't exist, I used errhint() instead. That means the hint message
might be translated twice, but I think that's minor and acceptable.
Or do you think we should instead backpatch errhint_internal() to
those older branches to avoid the double translation?Personally considered it can be added...
Just to confirm - you'd prefer backpatching errhint_internal() to v17 and
earlier branches, and then updating the patch to use it to avoid double
translation, right?
Regards,
--
Fujii Masao
Dear Fujii-san,
I first applied the patch to v15, then used git cherry-pick to backpatch it
to v14 and v13 without any issues. You can probably do the same to apply it
to those branches.
I did same approach and confirmed it could be applied well.
Cosmetic comments:
``` + if (!ReplicationSlotValidateNameInternal(name, +&err_code, &err_msg, &err_hint))
... -ReplicationSlotValidateName(const char *name, int elevel) +ReplicationSlotValidateNameInternal(const char *name, +int *err_code, char **err_msg, char **err_hint)
```
Patches for older branches have strange indent, maybe because
"bool allow_reserved_name" is just removed. Should we move up arguments?Since pgindent doesn't treat the current indentation as an issue,
I'm fine keeping it as is, though I don't mind changing it if you think
it's worth updating.
I do not have strong opinion neither, but I still think it can be updated.
Just to confirm - you'd prefer backpatching errhint_internal() to v17 and
earlier branches, and then updating the patch to use it to avoid double
translation, right?
Exactly, but I want to ask other Seniors as well.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
On Tue, Oct 21, 2025 at 2:36 PM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
Just to confirm - you'd prefer backpatching errhint_internal() to v17 and
earlier branches, and then updating the patch to use it to avoid double
translation, right?Exactly, but I want to ask other Seniors as well.
I don't think it is important enough to backpatch errhint_internal to
v17 and earlier branches. We can let double translation happen with
the use of errhint or we can even consider this as a HEAD only
improvement because this is not a blocking issue even when it happens.
--
With Regards,
Amit Kapila.
On Tue, Oct 21, 2025 at 7:27 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Oct 21, 2025 at 2:36 PM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:Just to confirm - you'd prefer backpatching errhint_internal() to v17 and
earlier branches, and then updating the patch to use it to avoid double
translation, right?Exactly, but I want to ask other Seniors as well.
I don't think it is important enough to backpatch errhint_internal to
v17 and earlier branches. We can let double translation happen with
the use of errhint or we can even consider this as a HEAD only
improvement because this is not a blocking issue even when it happens.
+1
So let's push the current patch, including only the cosmetic indentation
changes suggested by Hayato-san. *If* we later reach consensus on
backpatching errhint_internal(), we can handle that separately.
Since another patch [1]/messages/by-id/CAA5-nLCeO4MQzWipCXH58qf0arruiw0OeUc1+Q=Z=4GM+=v1NQ@mail.gmail.com seems to depend on this one, it's better not to
delay committing it.
Regards,
[1]: /messages/by-id/CAA5-nLCeO4MQzWipCXH58qf0arruiw0OeUc1+Q=Z=4GM+=v1NQ@mail.gmail.com
--
Fujii Masao
On Wed, Oct 22, 2025 at 12:52 PM Fujii Masao <masao.fujii@gmail.com> wrote:
So let's push the current patch, including only the cosmetic indentation
changes suggested by Hayato-san.
I've pushed the patch. Thanks!
Attached is the rebased version of the patch I shared earlier, which adds
GUC_check_errmsg_internal(), GUC_check_errdetail_internal(),
and GUC_check_errhint_internal(). These functions work like
their counterparts (e.g., GUC_check_errmsg()) but skip translation.
The patch also updates the check hook for primary_slot_name to use
these new functions when handling already-translated error messages.
If we apply this patch, I'm thinking to push it to master only.
Regards,
--
Fujii Masao
Attachments:
v7-0001-Avoid-double-translation-of-messages-for-invalid-.patchapplication/octet-stream; name=v7-0001-Avoid-double-translation-of-messages-for-invalid-.patchDownload
From a49a32782d7b7a323a6cc6fce877230299adb9bd Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Wed, 22 Oct 2025 22:06:42 +0900
Subject: [PATCH v7] Avoid double translation of messages for invalid
primary_slot_name.
When primary_slot_name is invalid, ReplicationSlotValidateNameInternal()
returns already translated error messages (when NLS is enabled). Previously,
the check hook for primary_slot_name passed these messages to
GUC_check_errdetail() and GUC_check_errhint(), which could retranslate
them and cause double translation.
To fix this, this commit adds GUC_check_errmsg_internal(),
GUC_check_errdetail_internal(), and GUC_check_errhint_internal().
These work like their counterparts but skip translation. The check hook
for primary_slot_name now uses these new functions when handling
messages from ReplicationSlotValidateNameInternal().
---
src/backend/access/transam/xlogrecovery.c | 10 ++++++--
src/backend/utils/error/elog.c | 31 +++++++++++++++++++++++
src/include/utils/elog.h | 1 +
src/include/utils/guc.h | 18 +++++++++++++
4 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..51374194130 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4769,10 +4769,16 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
!ReplicationSlotValidateNameInternal(*newval, false, &err_code,
&err_msg, &err_hint))
{
+ /*
+ * Use GUC_check_errdetail_internal() and GUC_check_errhint_internal()
+ * instead of GUC_check_errdetail() and GUC_check_errhint(), since the
+ * messages from ReplicationSlotValidateNameInternal() are already
+ * translated. This avoids double translation.
+ */
GUC_check_errcode(err_code);
- GUC_check_errdetail("%s", err_msg);
+ GUC_check_errdetail_internal("%s", err_msg);
if (err_hint != NULL)
- GUC_check_errhint("%s", err_hint);
+ GUC_check_errhint_internal("%s", err_hint);
return false;
}
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..83951d8ca4f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -1692,6 +1692,37 @@ format_elog_string(const char *fmt,...)
return edata->message;
}
+/*
+ * This is exactly like format_elog_string() except that strings passed to
+ * format_elog_string_internal are not translated, and are customarily left
+ * out of the internationalization message dictionary. This should be used
+ * when the passed strings have already been translated.
+ */
+char *
+format_elog_string_internal(const char *fmt,...)
+{
+ ErrorData errdata;
+ ErrorData *edata;
+ MemoryContext oldcontext;
+
+ /* Initialize a mostly-dummy error frame */
+ edata = &errdata;
+ MemSet(edata, 0, sizeof(ErrorData));
+ /* the default text domain is the backend's */
+ edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres");
+ /* set the errno to be used to interpret %m */
+ edata->saved_errno = save_format_errnumber;
+
+ oldcontext = MemoryContextSwitchTo(ErrorContext);
+
+ edata->message_id = fmt;
+ EVALUATE_MESSAGE(edata->domain, message, false, false);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return edata->message;
+}
+
/*
* Actual output of the top-of-stack error message
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 001ab93ae6c..259d35265cb 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -302,6 +302,7 @@ extern void errsave_finish(struct Node *context,
extern void pre_format_elog_string(int errnumber, const char *domain);
extern char *format_elog_string(const char *fmt,...) pg_attribute_printf(1, 2);
+extern char *format_elog_string_internal(const char *fmt,...) pg_attribute_printf(1, 2);
/* Support for attaching context information to error reports */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..af9f64c93ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -510,4 +510,22 @@ extern void GUC_check_errcode(int sqlerrcode);
pre_format_elog_string(errno, TEXTDOMAIN), \
GUC_check_errhint_string = format_elog_string
+/*
+ * These are exactly like GUC_check_errmsg/errdtail/errhint except that
+ * strings passed to them are not translated, and are customarily left
+ * out of the internationalization message dictionary. This should be used
+ * when the passed strings have already been translated.
+ */
+#define GUC_check_errmsg_internal \
+ pre_format_elog_string(errno, TEXTDOMAIN), \
+ GUC_check_errmsg_string = format_elog_string_internal
+
+#define GUC_check_errdetail_internal \
+ pre_format_elog_string(errno, TEXTDOMAIN), \
+ GUC_check_errdetail_string = format_elog_string_internal
+
+#define GUC_check_errhint_internal \
+ pre_format_elog_string(errno, TEXTDOMAIN), \
+ GUC_check_errhint_string = format_elog_string_internal
+
#endif /* GUC_H */
--
2.50.1