diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cb7afca9079..8cdd826fbd3 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4334,6 +4334,21 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
The precise stopping point is also influenced by
.
+
+
+ The value can be specified as either a 32-bit transaction ID or a 64-bit
+ transaction ID (consisting of an epoch and a 32-bit ID), such as the
+ value returned by pg_current_xact_id(). When a
+ 64-bit transaction ID is provided, only its 32-bit transaction ID
+ portion is used as the recovery target. For example, the values
+ 4294968296 (epoch 1) and 8589935592 (epoch 2) both refer to the same
+ 32-bit transaction ID, 1000.
+
+
+
+ The effective transaction ID (the 32-bit portion) must be greater than
+ or equal to 3.
+
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 31806dcf008..fbddd7e522c 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -5044,11 +5044,38 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
{
TransactionId xid;
TransactionId *myextra;
+ char *endp;
+ char *val;
errno = 0;
- xid = (TransactionId) strtou64(*newval, NULL, 0);
- if (errno == EINVAL || errno == ERANGE)
+
+ /*
+ * Consume leading whitespace to determine if number is negative
+ */
+ val = *newval;
+
+ while (isspace((unsigned char) *val))
+ val++;
+
+ /*
+ * This cast will remove the epoch, if any
+ */
+ xid = (TransactionId) strtou64(val, &endp, 0);
+
+ if (*endp != '\0' || errno == EINVAL || errno == ERANGE || *val == '-')
+ {
+ GUC_check_errdetail("\"%s\" is not a valid number.",
+ "recovery_target_xid");
+ return false;
+ }
+
+ if (xid < FirstNormalTransactionId)
+ {
+ GUC_check_errdetail("\"%s\" without epoch must be greater than or equal to %u.",
+ "recovery_target_xid",
+ FirstNormalTransactionId);
return false;
+ }
myextra = (TransactionId *) guc_malloc(LOG, sizeof(TransactionId));
if (!myextra)
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index e0df1a23423..4e36e3a3fb5 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -240,4 +240,26 @@ ok(!$res, 'invalid timeline target (upper bound check)');
$log_start =
$node_standby->wait_for_log("must be between 1 and 4294967295", $log_start);
+# Invalid recovery_target_xid tests
+my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
+ "ALTER SYSTEM SET recovery_target_xid TO 'bogus'");
+like(
+ $stderr,
+ qr/is not a valid number/,
+ "invalid recovery_target_xid (bogus value)");
+
+($result, $stdout, $stderr) = $node_primary->psql('postgres',
+ "ALTER SYSTEM SET recovery_target_xid TO '-1'");
+like(
+ $stderr,
+ qr/is not a valid number/,
+ "invalid recovery_target_xid (negative)");
+
+($result, $stdout, $stderr) = $node_primary->psql('postgres',
+ "ALTER SYSTEM SET recovery_target_xid TO '0'");
+like(
+ $stderr,
+ qr/without epoch must be greater than or equal to 3/,
+ "invalid recovery_target_xid (lower bound check)");
+
done_testing();