diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index db1a2d4e746..1e6e13c91c2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3221,7 +3221,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
recovery_target_lsn, recovery_target_name,
recovery_target_time, or recovery_target_xid
can be used; if more than one of these is specified in the configuration
- file, the last entry will be used.
+ file, an error will be raised.
These parameters can only be set at server start.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6497393c032..d469de73e97 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -11051,6 +11051,28 @@ assign_recovery_target_timeline(const char *newval, void *extra)
recoveryTargetTLIRequested = 0;
}
+/*
+ * Recovery target settings: Only one of the several recovery_target* settings
+ * may be set. Setting a second one results in an error. The global variable
+ * recoveryTarget tracks which kind of recovery target was chosen. Other
+ * variables store the actual target value (for example a string or a xid).
+ * The assign functions of the parameters check whether a competing parameter
+ * was already set. But we want to allow setting the same parameter multiple
+ * times. We also want to allow unsetting a parameter and setting a different
+ * one, so we unset recoveryTarget when the parameter is set to an empty
+ * string.
+ */
+
+static void
+pg_attribute_noreturn()
+error_multiple_recovery_targets(void)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("At most one of recovery_target, recovery_target_lsn, recovery_target_name, recovery_target_time, recovery_target_xid may be set.")));
+}
+
static bool
check_recovery_target(char **newval, void **extra, GucSource source)
{
@@ -11065,13 +11087,13 @@ check_recovery_target(char **newval, void **extra, GucSource source)
static void
assign_recovery_target(const char *newval, void *extra)
{
+ if (recoveryTarget != RECOVERY_TARGET_UNSET &&
+ recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
+ error_multiple_recovery_targets();
+
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
else
- /*
- * Reset recoveryTarget to RECOVERY_TARGET_UNSET to proper handle user
- * setting multiple recovery_target with blank value on last.
- */
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -11098,6 +11120,10 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
static void
assign_recovery_target_xid(const char *newval, void *extra)
{
+ if (recoveryTarget != RECOVERY_TARGET_UNSET &&
+ recoveryTarget != RECOVERY_TARGET_XID)
+ error_multiple_recovery_targets();
+
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
@@ -11161,6 +11187,10 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
static void
assign_recovery_target_time(const char *newval, void *extra)
{
+ if (recoveryTarget != RECOVERY_TARGET_UNSET &&
+ recoveryTarget != RECOVERY_TARGET_TIME)
+ error_multiple_recovery_targets();
+
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_TIME;
@@ -11186,6 +11216,10 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
static void
assign_recovery_target_name(const char *newval, void *extra)
{
+ if (recoveryTarget != RECOVERY_TARGET_UNSET &&
+ recoveryTarget != RECOVERY_TARGET_NAME)
+ error_multiple_recovery_targets();
+
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
@@ -11240,6 +11274,10 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
static void
assign_recovery_target_lsn(const char *newval, void *extra)
{
+ if (recoveryTarget != RECOVERY_TARGET_UNSET &&
+ recoveryTarget != RECOVERY_TARGET_LSN)
+ error_multiple_recovery_targets();
+
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index f6f2e8b745b..b46b318f5a3 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 9;
+use Test::More tests => 8;
# Create and test a standby from given backup, with a certain recovery target.
# Choose $until_lsn later than the transaction commit that causes the row
@@ -116,30 +116,22 @@ test_recovery_standby('LSN', 'standby_5', $node_master, \@recovery_params,
"5000", $lsn5);
# Multiple targets
-# Last entry has priority (note that an array respects the order of items
-# not hashes).
+#
+# Multiple conflicting settings are not allowed, but setting the same
+# parameter multiple times or unsetting a parameter and setting a
+# different one is allowed.
+
@recovery_params = (
- "recovery_target_name = '$recovery_name'",
- "recovery_target_xid = '$recovery_txid'",
- "recovery_target_time = '$recovery_time'");
-test_recovery_standby('name + XID + time',
- 'standby_6', $node_master, \@recovery_params, "3000", $lsn3);
-@recovery_params = (
- "recovery_target_time = '$recovery_time'",
- "recovery_target_name = '$recovery_name'",
- "recovery_target_xid = '$recovery_txid'");
-test_recovery_standby('time + name + XID',
- 'standby_7', $node_master, \@recovery_params, "2000", $lsn2);
-@recovery_params = (
- "recovery_target_xid = '$recovery_txid'",
- "recovery_target_time = '$recovery_time'",
- "recovery_target_name = '$recovery_name'");
-test_recovery_standby('XID + time + name',
- 'standby_8', $node_master, \@recovery_params, "4000", $lsn4);
-@recovery_params = (
- "recovery_target_xid = '$recovery_txid'",
- "recovery_target_time = '$recovery_time'",
- "recovery_target_name = '$recovery_name'",
- "recovery_target_lsn = '$recovery_lsn'",);
-test_recovery_standby('XID + time + name + LSN',
- 'standby_9', $node_master, \@recovery_params, "5000", $lsn5);
+ "recovery_target_name = '$recovery_name'",
+ "recovery_target_name = ''",
+ "recovery_target_time = '$recovery_time'");
+test_recovery_standby('multiple overriding settings',
+ 'standby_6', $node_master, \@recovery_params, "3000", $lsn3);
+
+my $node_standby = get_new_node('standby_7');
+$node_standby->init_from_backup($node_master, 'my_backup', has_restoring => 1);
+$node_standby->append_conf('postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+command_fails_like(['postgres', '-D', $node_standby->data_dir],
+ qr/multiple recovery targets specified/,
+ 'multiple conflicting settings');