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();