aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/Makefile4
-rw-r--r--src/backend/access/transam/recovery.conf.sample158
-rw-r--r--src/backend/access/transam/xlog.c500
-rw-r--r--src/backend/access/transam/xlogarchive.c4
-rw-r--r--src/backend/commands/extension.c4
-rw-r--r--src/backend/utils/misc/guc.c421
-rw-r--r--src/backend/utils/misc/postgresql.conf.sample47
-rw-r--r--src/bin/pg_archivecleanup/pg_archivecleanup.c2
-rw-r--r--src/bin/pg_basebackup/pg_basebackup.c148
-rw-r--r--src/bin/pg_basebackup/t/010_pg_basebackup.pl15
-rw-r--r--src/bin/pg_rewind/RewindTest.pm10
-rw-r--r--src/include/access/xlog.h39
-rw-r--r--src/include/utils/guc_tables.h2
-rw-r--r--src/port/quotes.c2
-rw-r--r--src/test/perl/PostgresNode.pm26
-rw-r--r--src/test/recovery/t/001_stream_rep.pl4
-rw-r--r--src/test/recovery/t/003_recovery_targets.pl2
-rw-r--r--src/test/recovery/t/004_timeline_switch.pl4
-rw-r--r--src/test/recovery/t/005_replay_delay.pl2
-rw-r--r--src/test/recovery/t/009_twophase.pl6
-rw-r--r--src/test/recovery/t/010_logical_decoding_timelines.pl2
-rw-r--r--src/test/recovery/t/012_subtransactions.pl6
22 files changed, 828 insertions, 580 deletions
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 3a58bf6685b..25eb0439416 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -209,7 +209,6 @@ endif
$(INSTALL_DATA) $(srcdir)/libpq/pg_hba.conf.sample '$(DESTDIR)$(datadir)/pg_hba.conf.sample'
$(INSTALL_DATA) $(srcdir)/libpq/pg_ident.conf.sample '$(DESTDIR)$(datadir)/pg_ident.conf.sample'
$(INSTALL_DATA) $(srcdir)/utils/misc/postgresql.conf.sample '$(DESTDIR)$(datadir)/postgresql.conf.sample'
- $(INSTALL_DATA) $(srcdir)/access/transam/recovery.conf.sample '$(DESTDIR)$(datadir)/recovery.conf.sample'
ifeq ($(with_llvm), yes)
install-bin: install-postgres-bitcode
@@ -274,8 +273,7 @@ endif
$(MAKE) -C utils uninstall-data
rm -f '$(DESTDIR)$(datadir)/pg_hba.conf.sample' \
'$(DESTDIR)$(datadir)/pg_ident.conf.sample' \
- '$(DESTDIR)$(datadir)/postgresql.conf.sample' \
- '$(DESTDIR)$(datadir)/recovery.conf.sample'
+ '$(DESTDIR)$(datadir)/postgresql.conf.sample'
ifeq ($(with_llvm), yes)
$(call uninstall_llvm_module,postgres)
endif
diff --git a/src/backend/access/transam/recovery.conf.sample b/src/backend/access/transam/recovery.conf.sample
deleted file mode 100644
index 8e466126428..00000000000
--- a/src/backend/access/transam/recovery.conf.sample
+++ /dev/null
@@ -1,158 +0,0 @@
-# -------------------------------
-# PostgreSQL recovery config file
-# -------------------------------
-#
-# Edit this file to provide the parameters that PostgreSQL needs to
-# perform an archive recovery of a database, or to act as a replication
-# standby.
-#
-# If "recovery.conf" is present in the PostgreSQL data directory, it is
-# read on postmaster startup. After successful recovery, it is renamed
-# to "recovery.done" to ensure that we do not accidentally re-enter
-# archive recovery or standby mode.
-#
-# This file consists of lines of the form:
-#
-# name = value
-#
-# Comments are introduced with '#'.
-#
-# The complete list of option names and allowed values can be found
-# in the PostgreSQL documentation.
-#
-#---------------------------------------------------------------------------
-# ARCHIVE RECOVERY PARAMETERS
-#---------------------------------------------------------------------------
-#
-# restore_command
-#
-# specifies the shell command that is executed to copy log files
-# back from archival storage. The command string may contain %f,
-# which is replaced by the name of the desired log file, and %p,
-# which is replaced by the absolute path to copy the log file to.
-#
-# This parameter is *required* for an archive recovery, but optional
-# for streaming replication.
-#
-# It is important that the command return nonzero exit status on failure.
-# The command *will* be asked for log files that are not present in the
-# archive; it must return nonzero when so asked.
-#
-# NOTE that the basename of %p will be different from %f; do not
-# expect them to be interchangeable.
-#
-#restore_command = '' # e.g. 'cp /mnt/server/archivedir/%f %p'
-#
-#
-# archive_cleanup_command
-#
-# specifies an optional shell command to execute at every restartpoint.
-# This can be useful for cleaning up the archive of a standby server.
-#
-#archive_cleanup_command = ''
-#
-# recovery_end_command
-#
-# specifies an optional shell command to execute at completion of recovery.
-# This can be useful for cleaning up after the restore_command.
-#
-#recovery_end_command = ''
-#
-#---------------------------------------------------------------------------
-# RECOVERY TARGET PARAMETERS
-#---------------------------------------------------------------------------
-#
-# By default, recovery will rollforward to the end of the WAL log.
-# If you want to stop rollforward at a specific point, you
-# must set a recovery target.
-#
-# You may set a recovery target either by transactionId, by name, by
-# timestamp or by WAL location (LSN). Recovery may either include or
-# exclude the transaction(s) with the recovery target value (i.e.,
-# stop either just after or just before the given target,
-# respectively).
-#
-#
-#recovery_target_name = '' # e.g. 'daily backup 2011-01-26'
-#
-#recovery_target_time = '' # e.g. '2004-07-14 22:39:00 EST'
-#
-#recovery_target_xid = ''
-#
-#recovery_target_lsn = '' # e.g. '0/70006B8'
-#
-#recovery_target_inclusive = true
-#
-#
-# Alternatively, you can request stopping as soon as a consistent state
-# is reached, by uncommenting this option.
-#
-#recovery_target = 'immediate'
-#
-#
-# If you want to recover into a timeline other than the "main line" shown in
-# pg_control, specify the timeline number here, or write 'latest' to get
-# the latest branch for which there's a history file.
-#
-#recovery_target_timeline = 'latest'
-#
-#
-# If recovery_target_action = 'pause', recovery will pause when the
-# recovery target is reached. The pause state will continue until
-# pg_wal_replay_resume() is called. This setting has no effect if
-# no recovery target is set. If hot_standby is not enabled then the
-# server will shutdown instead, though you may request this in
-# any case by specifying 'shutdown'.
-#
-#recovery_target_action = 'pause'
-#
-#---------------------------------------------------------------------------
-# STANDBY SERVER PARAMETERS
-#---------------------------------------------------------------------------
-#
-# standby_mode
-#
-# When standby_mode is enabled, the PostgreSQL server will work as a
-# standby. It will continuously wait for the additional XLOG records, using
-# restore_command and/or primary_conninfo.
-#
-#standby_mode = off
-#
-# primary_conninfo
-#
-# If set, the PostgreSQL server will try to connect to the primary using this
-# connection string and receive XLOG records continuously.
-#
-#primary_conninfo = '' # e.g. 'host=localhost port=5432'
-#
-# If set, the PostgreSQL server will use the specified replication slot when
-# connecting to the primary via streaming replication to control resource
-# removal on the upstream node. This setting has no effect if primary_conninfo
-# is not set.
-#
-#primary_slot_name = ''
-#
-# By default, a standby server keeps restoring XLOG records from the
-# primary indefinitely. If you want to stop the standby mode, finish recovery
-# and open the system in read/write mode, specify a path to a trigger file.
-# The server will poll the trigger file path periodically and start as a
-# primary server when it's found.
-#
-#trigger_file = ''
-#
-# By default, a standby server restores XLOG records from the primary as
-# soon as possible. If you want to explicitly delay the replay of committed
-# transactions from the master, specify a minimum apply delay. For example,
-# if you set this parameter to 5min, the standby will replay each transaction
-# commit only when the system time on the standby is at least five minutes
-# past the commit time reported by the master.
-#
-#recovery_min_apply_delay = 0
-#
-#---------------------------------------------------------------------------
-# HOT STANDBY PARAMETERS
-#---------------------------------------------------------------------------
-#
-# Hot Standby related parameters are listed in postgresql.conf
-#
-#---------------------------------------------------------------------------
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7397b6ee06b..c80b14ed971 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -69,7 +69,6 @@
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/memutils.h"
-#include "utils/pg_lsn.h"
#include "utils/ps_status.h"
#include "utils/relmapper.h"
#include "utils/snapmgr.h"
@@ -78,6 +77,9 @@
extern uint32 bootstrap_data_checksum_version;
+/* Unsupported old recovery command file names (relative to $PGDATA) */
+#define RECOVERY_COMMAND_FILE "recovery.conf"
+#define RECOVERY_COMMAND_DONE "recovery.done"
/* User-settable parameters */
int max_wal_size_mb = 1024; /* 1 GB */
@@ -161,6 +163,13 @@ const struct config_enum_entry archive_mode_options[] = {
{NULL, 0, false}
};
+const struct config_enum_entry recovery_target_action_options[] = {
+ {"pause", RECOVERY_TARGET_ACTION_PAUSE, false},
+ {"promote", RECOVERY_TARGET_ACTION_PROMOTE, false},
+ {"shutdown", RECOVERY_TARGET_ACTION_SHUTDOWN, false},
+ {NULL, 0, false}
+};
+
/*
* Statistics for current checkpoint are collected in this global struct.
* Because only the checkpointer or a stand-alone backend can perform
@@ -230,7 +239,7 @@ static int LocalXLogInsertAllowed = -1;
/*
* When ArchiveRecoveryRequested is set, archive recovery was requested,
- * ie. recovery.conf file was present. When InArchiveRecovery is set, we are
+ * ie. signal files were present. When InArchiveRecovery is set, we are
* currently recovering using offline XLOG archives. These variables are only
* valid in the startup process.
*
@@ -242,6 +251,9 @@ static int LocalXLogInsertAllowed = -1;
bool ArchiveRecoveryRequested = false;
bool InArchiveRecovery = false;
+static bool standby_signal_file_found = false;
+static bool recovery_signal_file_found = false;
+
/* Was the last xlog file restored from archive, or local? */
static bool restoredFromArchive = false;
@@ -249,25 +261,25 @@ static bool restoredFromArchive = false;
static char *replay_image_masked = NULL;
static char *master_image_masked = NULL;
-/* options taken from recovery.conf for archive recovery */
+/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
-static char *recoveryEndCommand = NULL;
-static char *archiveCleanupCommand = NULL;
-static RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
-static bool recoveryTargetInclusive = true;
-static RecoveryTargetAction recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
-static TransactionId recoveryTargetXid;
-static TimestampTz recoveryTargetTime;
-static char *recoveryTargetName;
-static XLogRecPtr recoveryTargetLSN;
-static int recovery_min_apply_delay = 0;
-static TimestampTz recoveryDelayUntilTime;
-
-/* options taken from recovery.conf for XLOG streaming */
-static bool StandbyModeRequested = false;
-static char *PrimaryConnInfo = NULL;
-static char *PrimarySlotName = NULL;
-static char *TriggerFile = NULL;
+char *recoveryEndCommand = NULL;
+char *archiveCleanupCommand = NULL;
+RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
+bool recoveryTargetInclusive = true;
+int recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
+TransactionId recoveryTargetXid;
+TimestampTz recoveryTargetTime;
+char *recoveryTargetName;
+XLogRecPtr recoveryTargetLSN;
+int recovery_min_apply_delay = 0;
+TimestampTz recoveryDelayUntilTime;
+
+/* options formerly taken from recovery.conf for XLOG streaming */
+bool StandbyModeRequested = false;
+char *PrimaryConnInfo = NULL;
+char *PrimarySlotName = NULL;
+char *PromoteTriggerFile = NULL;
/* are we currently in standby mode? */
bool StandbyMode = false;
@@ -293,7 +305,11 @@ static bool recoveryStopAfter;
* the currently-scanned WAL record was generated). We also need these
* timeline values:
*
- * recoveryTargetTLI: the desired timeline that we want to end in.
+ * recoveryTargetTimeLineGoal: what the user requested, if any
+ *
+ * recoveryTargetTLIRequested: numeric value of requested timeline, if constant
+ *
+ * recoveryTargetTLI: the currently understood target timeline; changes
*
* recoveryTargetIsLatest: was the requested target timeline 'latest'?
*
@@ -309,8 +325,9 @@ static bool recoveryStopAfter;
* file was created.) During a sequential scan we do not allow this value
* to decrease.
*/
-static TimeLineID recoveryTargetTLI;
-static bool recoveryTargetIsLatest = false;
+RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
+TimeLineID recoveryTargetTLIRequested = 0;
+TimeLineID recoveryTargetTLI = 0;
static List *expectedTLEs;
static TimeLineID curFileTLI;
@@ -625,12 +642,6 @@ typedef struct XLogCtlData
TimeLineID PrevTimeLineID;
/*
- * archiveCleanupCommand is read from recovery.conf but needs to be in
- * shared memory so that the checkpointer process can access it.
- */
- char archiveCleanupCommand[MAXPGPATH];
-
- /*
* SharedRecoveryInProgress indicates if we're still in crash or archive
* recovery. Protected by info_lck.
*/
@@ -846,7 +857,8 @@ static bool holdingAllLocks = false;
static MemoryContext walDebugCxt = NULL;
#endif
-static void readRecoveryCommandFile(void);
+static void readRecoverySignalFile(void);
+static void validateRecoveryParameters(void);
static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog);
static bool recoveryStopsBefore(XLogReaderState *record);
static bool recoveryStopsAfter(XLogReaderState *record);
@@ -5285,283 +5297,111 @@ str_time(pg_time_t tnow)
}
/*
- * See if there is a recovery command file (recovery.conf), and if so
- * read in parameters for archive recovery and XLOG streaming.
+ * See if there are any recovery signal files and if so, set state for
+ * recovery.
*
- * The file is parsed using the main configuration parser.
+ * See if there is a recovery command file (recovery.conf), and if so
+ * throw an ERROR since as of PG12 we no longer recognize that.
*/
static void
-readRecoveryCommandFile(void)
+readRecoverySignalFile(void)
{
- FILE *fd;
- TimeLineID rtli = 0;
- bool rtliGiven = false;
- ConfigVariable *item,
- *head = NULL,
- *tail = NULL;
- bool recoveryTargetActionSet = false;
+ struct stat stat_buf;
+ if (IsBootstrapProcessingMode())
+ return;
- fd = AllocateFile(RECOVERY_COMMAND_FILE, "r");
- if (fd == NULL)
- {
- if (errno == ENOENT)
- return; /* not there, so no archive recovery */
+ /*
+ * Check for old recovery API file: recovery.conf
+ */
+ if (stat(RECOVERY_COMMAND_FILE, &stat_buf) == 0)
ereport(FATAL,
(errcode_for_file_access(),
- errmsg("could not open recovery command file \"%s\": %m",
+ errmsg("using recovery command file \"%s\" is not supported",
RECOVERY_COMMAND_FILE)));
- }
/*
- * Since we're asking ParseConfigFp() to report errors as FATAL, there's
- * no need to check the return value.
+ * Remove unused .done file, if present. Ignore if absent.
*/
- (void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail);
-
- FreeFile(fd);
+ unlink(RECOVERY_COMMAND_DONE);
- for (item = head; item; item = item->next)
+ /*
+ * Check for recovery signal files and if found, fsync them since they
+ * represent server state information.
+ *
+ * If present, standby signal file takes precedence. If neither is present
+ * then we won't enter archive recovery.
+ */
+ if (stat(STANDBY_SIGNAL_FILE, &stat_buf) == 0)
{
- if (strcmp(item->name, "restore_command") == 0)
- {
- recoveryRestoreCommand = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("restore_command = '%s'",
- recoveryRestoreCommand)));
- }
- else if (strcmp(item->name, "recovery_end_command") == 0)
- {
- recoveryEndCommand = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("recovery_end_command = '%s'",
- recoveryEndCommand)));
- }
- else if (strcmp(item->name, "archive_cleanup_command") == 0)
- {
- archiveCleanupCommand = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("archive_cleanup_command = '%s'",
- archiveCleanupCommand)));
- }
- else if (strcmp(item->name, "recovery_target_action") == 0)
- {
- if (strcmp(item->value, "pause") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
- else if (strcmp(item->value, "promote") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_PROMOTE;
- else if (strcmp(item->value, "shutdown") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
- "recovery_target_action",
- item->value),
- errhint("Valid values are \"pause\", \"promote\", and \"shutdown\".")));
-
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_action = '%s'",
- item->value)));
-
- recoveryTargetActionSet = true;
- }
- else if (strcmp(item->name, "recovery_target_timeline") == 0)
- {
- rtliGiven = true;
- if (strcmp(item->value, "latest") == 0)
- rtli = 0;
- else
- {
- errno = 0;
- rtli = (TimeLineID) strtoul(item->value, NULL, 0);
- if (errno == EINVAL || errno == ERANGE)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_timeline is not a valid number: \"%s\"",
- item->value)));
- }
- if (rtli)
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_timeline = %u", rtli)));
- else
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_timeline = latest")));
- }
- else if (strcmp(item->name, "recovery_target_xid") == 0)
- {
- errno = 0;
- recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
- if (errno == EINVAL || errno == ERANGE)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_xid is not a valid number: \"%s\"",
- item->value)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_xid = %u",
- recoveryTargetXid)));
- recoveryTarget = RECOVERY_TARGET_XID;
- }
- else if (strcmp(item->name, "recovery_target_time") == 0)
- {
- recoveryTarget = RECOVERY_TARGET_TIME;
-
- if (strcmp(item->value, "epoch") == 0 ||
- strcmp(item->value, "infinity") == 0 ||
- strcmp(item->value, "-infinity") == 0 ||
- strcmp(item->value, "now") == 0 ||
- strcmp(item->value, "today") == 0 ||
- strcmp(item->value, "tomorrow") == 0 ||
- strcmp(item->value, "yesterday") == 0)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_time is not a valid timestamp: \"%s\"",
- item->value)));
+ int fd;
- /*
- * Convert the time string given by the user to TimestampTz form.
- */
- recoveryTargetTime =
- DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
- CStringGetDatum(item->value),
- ObjectIdGetDatum(InvalidOid),
- Int32GetDatum(-1)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_time = '%s'",
- timestamptz_to_str(recoveryTargetTime))));
- }
- else if (strcmp(item->name, "recovery_target_name") == 0)
- {
- recoveryTarget = RECOVERY_TARGET_NAME;
+ fd = BasicOpenFilePerm(STANDBY_SIGNAL_FILE, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
+ S_IRUSR | S_IWUSR);
+ pg_fsync(fd);
+ close(fd);
+ standby_signal_file_found = true;
+ }
+ else if (stat(RECOVERY_SIGNAL_FILE, &stat_buf) == 0)
+ {
+ int fd;
- recoveryTargetName = pstrdup(item->value);
- if (strlen(recoveryTargetName) >= MAXFNAMELEN)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_name is too long (maximum %d characters)",
- MAXFNAMELEN - 1)));
+ fd = BasicOpenFilePerm(RECOVERY_SIGNAL_FILE, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
+ S_IRUSR | S_IWUSR);
+ pg_fsync(fd);
+ close(fd);
+ recovery_signal_file_found = true;
+ }
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_name = '%s'",
- recoveryTargetName)));
- }
- else if (strcmp(item->name, "recovery_target_lsn") == 0)
- {
- recoveryTarget = RECOVERY_TARGET_LSN;
+ StandbyModeRequested = false;
+ ArchiveRecoveryRequested = false;
+ if (standby_signal_file_found)
+ {
+ StandbyModeRequested = true;
+ ArchiveRecoveryRequested = true;
+ }
+ else if (recovery_signal_file_found)
+ {
+ StandbyModeRequested = false;
+ ArchiveRecoveryRequested = true;
+ }
+ else
+ return;
- /*
- * Convert the LSN string given by the user to XLogRecPtr form.
- */
- recoveryTargetLSN =
- DatumGetLSN(DirectFunctionCall3(pg_lsn_in,
- CStringGetDatum(item->value),
- ObjectIdGetDatum(InvalidOid),
- Int32GetDatum(-1)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_lsn = '%X/%X'",
- (uint32) (recoveryTargetLSN >> 32),
- (uint32) recoveryTargetLSN)));
- }
- else if (strcmp(item->name, "recovery_target") == 0)
- {
- if (strcmp(item->value, "immediate") == 0)
- recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
- "recovery_target",
- item->value),
- errhint("The only allowed value is \"immediate\".")));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target = '%s'",
- item->value)));
- }
- else if (strcmp(item->name, "recovery_target_inclusive") == 0)
- {
- /*
- * does nothing if a recovery_target is not also set
- */
- if (!parse_bool(item->value, &recoveryTargetInclusive))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a Boolean value",
- "recovery_target_inclusive")));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_inclusive = %s",
- item->value)));
- }
- else if (strcmp(item->name, "standby_mode") == 0)
- {
- if (!parse_bool(item->value, &StandbyModeRequested))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a Boolean value",
- "standby_mode")));
- ereport(DEBUG2,
- (errmsg_internal("standby_mode = '%s'", item->value)));
- }
- else if (strcmp(item->name, "primary_conninfo") == 0)
- {
- PrimaryConnInfo = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("primary_conninfo = '%s'",
- PrimaryConnInfo)));
- }
- else if (strcmp(item->name, "primary_slot_name") == 0)
- {
- ReplicationSlotValidateName(item->value, ERROR);
- PrimarySlotName = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("primary_slot_name = '%s'",
- PrimarySlotName)));
- }
- else if (strcmp(item->name, "trigger_file") == 0)
- {
- TriggerFile = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("trigger_file = '%s'",
- TriggerFile)));
- }
- else if (strcmp(item->name, "recovery_min_apply_delay") == 0)
- {
- const char *hintmsg;
+ /*
+ * We don't support standby mode in standalone backends; that requires
+ * other processes such as the WAL receiver to be alive.
+ */
+ if (StandbyModeRequested && !IsUnderPostmaster)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("standby mode is not supported by single-user servers")));
+}
- if (!parse_int(item->value, &recovery_min_apply_delay, GUC_UNIT_MS,
- &hintmsg))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a temporal value",
- "recovery_min_apply_delay"),
- hintmsg ? errhint("%s", _(hintmsg)) : 0));
- ereport(DEBUG2,
- (errmsg_internal("recovery_min_apply_delay = '%s'", item->value)));
- }
- else
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized recovery parameter \"%s\"",
- item->name)));
- }
+static void
+validateRecoveryParameters(void)
+{
+ if (!ArchiveRecoveryRequested)
+ return;
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
- if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL)
+ if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
+ (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
ereport(WARNING,
- (errmsg("recovery command file \"%s\" specified neither primary_conninfo nor restore_command",
- RECOVERY_COMMAND_FILE),
+ (errmsg("specified neither primary_conninfo nor restore_command"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL)
+ if (recoveryRestoreCommand == NULL ||
+ strcmp(recoveryRestoreCommand, "") == 0)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery command file \"%s\" must specify restore_command when standby mode is not enabled",
- RECOVERY_COMMAND_FILE)));
+ errmsg("must specify restore_command when standby mode is not enabled")));
}
/*
@@ -5570,50 +5410,40 @@ readRecoveryCommandFile(void)
* hot_standby = off, which was surprising behaviour.
*/
if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE &&
- recoveryTargetActionSet &&
!EnableHotStandby)
recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
/*
- * We don't support standby_mode in standalone backends; that requires
- * other processes such as the WAL receiver to be alive.
- */
- if (StandbyModeRequested && !IsUnderPostmaster)
- ereport(FATAL,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("standby mode is not supported by single-user servers")));
-
- /* Enable fetching from archive recovery area */
- ArchiveRecoveryRequested = true;
-
- /*
* If user specified recovery_target_timeline, validate it or compute the
* "latest" value. We can't do this until after we've gotten the restore
* command and set InArchiveRecovery, because we need to fetch timeline
* history files from the archive.
*/
- if (rtliGiven)
+ if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC)
{
- if (rtli)
- {
- /* Timeline 1 does not have a history file, all else should */
- if (rtli != 1 && !existsTimeLineHistory(rtli))
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery target timeline %u does not exist",
- rtli)));
- recoveryTargetTLI = rtli;
- recoveryTargetIsLatest = false;
- }
- else
- {
- /* We start the "latest" search from pg_control's timeline */
- recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
- recoveryTargetIsLatest = true;
- }
- }
+ TimeLineID rtli = recoveryTargetTLIRequested;
- FreeConfigVariables(head);
+ /* Timeline 1 does not have a history file, all else should */
+ if (rtli != 1 && !existsTimeLineHistory(rtli))
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("recovery target timeline %u does not exist",
+ rtli)));
+ recoveryTargetTLI = rtli;
+ }
+ else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
+ {
+ /* We start the "latest" search from pg_control's timeline */
+ recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
+ }
+ else
+ {
+ /*
+ * else we just use the recoveryTargetTLI as already read from
+ * ControlFile
+ */
+ Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE);
+ }
}
/*
@@ -5714,11 +5544,14 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
unlink(recoveryPath); /* ignore any error */
/*
- * Rename the config file out of the way, so that we don't accidentally
+ * Remove the signal files out of the way, so that we don't accidentally
* re-enter archive recovery mode in a subsequent crash.
*/
- unlink(RECOVERY_COMMAND_DONE);
- durable_rename(RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE, FATAL);
+ if (standby_signal_file_found)
+ durable_unlink(STANDBY_SIGNAL_FILE, FATAL);
+
+ if (recovery_signal_file_found)
+ durable_unlink(RECOVERY_SIGNAL_FILE, FATAL);
ereport(LOG,
(errmsg("archive recovery complete")));
@@ -6461,18 +6294,10 @@ StartupXLOG(void)
recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
/*
- * Check for recovery control file, and if so set up state for offline
- * recovery
- */
- readRecoveryCommandFile();
-
- /*
- * Save archive_cleanup_command in shared memory so that other processes
- * can see it.
+ * Check for signal files, and if so set up state for offline recovery
*/
- strlcpy(XLogCtl->archiveCleanupCommand,
- archiveCleanupCommand ? archiveCleanupCommand : "",
- sizeof(XLogCtl->archiveCleanupCommand));
+ readRecoverySignalFile();
+ validateRecoveryParameters();
if (ArchiveRecoveryRequested)
{
@@ -6652,7 +6477,8 @@ StartupXLOG(void)
* This can happen for example if a base backup is taken from a
* running server using an atomic filesystem snapshot, without calling
* pg_start/stop_backup. Or if you just kill a running master server
- * and put it into archive recovery by creating a recovery.conf file.
+ * and put it into archive recovery by creating a recovery signal
+ * file.
*
* Our strategy in that case is to perform crash recovery first,
* replaying all the WAL present in pg_wal, and only enter archive
@@ -6687,7 +6513,7 @@ StartupXLOG(void)
{
/*
* We used to attempt to go back to a secondary checkpoint record
- * here, but only when not in standby_mode. We now just fail if we
+ * here, but only when not in standby mode. We now just fail if we
* can't read the last checkpoint because this allows us to
* simplify processing around checkpoints.
*/
@@ -6878,7 +6704,7 @@ StartupXLOG(void)
/*
* Check whether we need to force recovery from WAL. If it appears to
- * have been a clean shutdown and we did not have a recovery.conf file,
+ * have been a clean shutdown and we did not have a recovery signal file,
* then assume no recovery needed.
*/
if (checkPoint.redo < RecPtr)
@@ -6892,7 +6718,7 @@ StartupXLOG(void)
InRecovery = true;
else if (ArchiveRecoveryRequested)
{
- /* force recovery due to presence of recovery.conf */
+ /* force recovery due to presence of recovery signal file */
InRecovery = true;
}
@@ -7763,7 +7589,7 @@ StartupXLOG(void)
/*
* And finally, execute the recovery_end_command, if any.
*/
- if (recoveryEndCommand)
+ if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
ExecuteRecoveryCommand(recoveryEndCommand,
"recovery_end_command",
true);
@@ -9485,8 +9311,8 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
*/
- if (XLogCtl->archiveCleanupCommand[0])
- ExecuteRecoveryCommand(XLogCtl->archiveCleanupCommand,
+ if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ ExecuteRecoveryCommand(archiveCleanupCommand,
"archive_cleanup_command",
false);
@@ -11995,7 +11821,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
* that when we later jump backwards to start redo at
* RedoStartLSN, we will have the logs streamed already.
*/
- if (PrimaryConnInfo)
+ if (PrimaryConnInfo && strcmp(PrimaryConnInfo, "") != 0)
{
XLogRecPtr ptr;
TimeLineID tli;
@@ -12064,7 +11890,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
* Before we sleep, re-scan for possible new timelines if
* we were requested to recover to the latest timeline.
*/
- if (recoveryTargetIsLatest)
+ if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
{
if (rescanLatestTimeLine())
{
@@ -12367,14 +12193,14 @@ CheckForStandbyTrigger(void)
return true;
}
- if (TriggerFile == NULL)
+ if (PromoteTriggerFile == NULL || strcmp(PromoteTriggerFile, "") == 0)
return false;
- if (stat(TriggerFile, &stat_buf) == 0)
+ if (stat(PromoteTriggerFile, &stat_buf) == 0)
{
ereport(LOG,
- (errmsg("trigger file found: %s", TriggerFile)));
- unlink(TriggerFile);
+ (errmsg("promote trigger file found: %s", PromoteTriggerFile)));
+ unlink(PromoteTriggerFile);
triggered = true;
fast_promote = true;
return true;
@@ -12382,8 +12208,8 @@ CheckForStandbyTrigger(void)
else if (errno != ENOENT)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not stat trigger file \"%s\": %m",
- TriggerFile)));
+ errmsg("could not stat promote trigger file \"%s\": %m",
+ PromoteTriggerFile)));
return false;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index d40317168e2..b8da714b800 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -66,7 +66,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
TimeLineID restartTli;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL)
+ if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
goto not_available;
/*
@@ -410,7 +410,7 @@ ExecuteRecoveryCommand(const char *command, const char *commandName, bool failOn
ereport((signaled && failOnSignal) ? FATAL : WARNING,
/*------
- translator: First %s represents a recovery.conf parameter name like
+ translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
third an already translated error message. */
(errmsg("%s \"%s\": %s", commandName,
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a587a1bc03e..87e4dd82453 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -9,8 +9,8 @@
* dependent objects can be associated with it. An extension is created by
* populating the pg_extension catalog from a "control" file.
* The extension control file is parsed with the same parser we use for
- * postgresql.conf and recovery.conf. An extension also has an installation
- * script file, containing SQL commands to create the extension's objects.
+ * postgresql.conf. An extension also has an installation script file,
+ * containing SQL commands to create the extension's objects.
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0ec3ff0fd6b..6497393c032 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -84,6 +84,7 @@
#include "utils/float.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
+#include "utils/pg_lsn.h"
#include "utils/plancache.h"
#include "utils/portal.h"
#include "utils/ps_status.h"
@@ -195,6 +196,19 @@ static bool check_cluster_name(char **newval, void **extra, GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
static const char *show_data_directory_mode(void);
+static bool check_recovery_target_timeline(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_timeline(const char *newval, void *extra);
+static bool check_recovery_target(char **newval, void **extra, GucSource source);
+static void assign_recovery_target(const char *newval, void *extra);
+static bool check_recovery_target_xid(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_xid(const char *newval, void *extra);
+static bool check_recovery_target_time(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_time(const char *newval, void *extra);
+static bool check_recovery_target_name(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_name(const char *newval, void *extra);
+static bool check_recovery_target_lsn(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_lsn(const char *newval, void *extra);
+static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -442,6 +456,7 @@ const struct config_enum_entry ssl_protocol_versions_info[] = {
*/
extern const struct config_enum_entry wal_level_options[];
extern const struct config_enum_entry archive_mode_options[];
+extern const struct config_enum_entry recovery_target_action_options[];
extern const struct config_enum_entry sync_method_options[];
extern const struct config_enum_entry dynamic_shared_memory_options[];
@@ -533,6 +548,13 @@ static int wal_block_size;
static bool data_checksums;
static bool integer_datetimes;
static bool assert_enabled;
+static char *recovery_target_timeline_string;
+static char *recovery_target_string;
+static char *recovery_target_xid_string;
+static char *recovery_target_time_string;
+static char *recovery_target_name_string;
+static char *recovery_target_lsn_string;
+
/* should be static, but commands/variable.c needs to get at this */
char *role_string;
@@ -616,6 +638,10 @@ const char *const config_group_names[] =
gettext_noop("Write-Ahead Log / Checkpoints"),
/* WAL_ARCHIVING */
gettext_noop("Write-Ahead Log / Archiving"),
+ /* WAL_ARCHIVE_RECOVERY */
+ gettext_noop("Write-Ahead Log / Archive Recovery"),
+ /* WAL_RECOVERY_TARGET */
+ gettext_noop("Write-Ahead Log / Recovery Target"),
/* REPLICATION */
gettext_noop("Replication"),
/* REPLICATION_SENDING */
@@ -1638,6 +1664,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"recovery_target_inclusive", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets whether to include or exclude transaction with recovery target."),
+ NULL
+ },
+ &recoveryTargetInclusive,
+ true,
+ NULL, NULL, NULL
+ },
+
+ {
{"hot_standby", PGC_POSTMASTER, REPLICATION_STANDBY,
gettext_noop("Allows connections and queries during recovery."),
NULL
@@ -1974,6 +2010,17 @@ static struct config_int ConfigureNamesInt[] =
},
{
+ {"recovery_min_apply_delay", PGC_POSTMASTER, REPLICATION_STANDBY,
+ gettext_noop("Sets the minimum delay for applying changes during recovery."),
+ NULL,
+ GUC_UNIT_MS
+ },
+ &recovery_min_apply_delay,
+ 0, 0, INT_MAX,
+ NULL, NULL, NULL
+ },
+
+ {
{"wal_receiver_status_interval", PGC_SIGHUP, REPLICATION_STANDBY,
gettext_noop("Sets the maximum interval between WAL receiver status reports to the sending server."),
NULL,
@@ -3292,6 +3339,123 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"restore_command", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the shell command that will retrieve an archived WAL file."),
+ NULL
+ },
+ &recoveryRestoreCommand,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"archive_cleanup_command", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the shell command that will be executed at every restart point."),
+ NULL
+ },
+ &archiveCleanupCommand,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"recovery_end_command", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the shell command that will be executed once at the end of recovery."),
+ NULL
+ },
+ &recoveryEndCommand,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"recovery_target_timeline", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Specifies the timeline to recovery into."),
+ NULL
+ },
+ &recovery_target_timeline_string,
+ "",
+ check_recovery_target_timeline, assign_recovery_target_timeline, NULL
+ },
+
+ {
+ {"recovery_target", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Set to 'immediate' to end recovery as soon as a consistent state is reached."),
+ NULL
+ },
+ &recovery_target_string,
+ "",
+ check_recovery_target, assign_recovery_target, NULL
+ },
+ {
+ {"recovery_target_xid", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the transaction ID up to which recovery will proceed."),
+ NULL
+ },
+ &recovery_target_xid_string,
+ "",
+ check_recovery_target_xid, assign_recovery_target_xid, NULL
+ },
+ {
+ {"recovery_target_time", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the time stamp up to which recovery will proceed."),
+ NULL
+ },
+ &recovery_target_time_string,
+ "",
+ check_recovery_target_time, assign_recovery_target_time, NULL
+ },
+ {
+ {"recovery_target_name", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the named restore point up to which recovery will proceed."),
+ NULL
+ },
+ &recovery_target_name_string,
+ "",
+ check_recovery_target_name, assign_recovery_target_name, NULL
+ },
+ {
+ {"recovery_target_lsn", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the LSN of the write-ahead log location up to which recovery will proceed."),
+ NULL
+ },
+ &recovery_target_lsn_string,
+ "",
+ check_recovery_target_lsn, assign_recovery_target_lsn, NULL
+ },
+
+ {
+ {"promote_trigger_file", PGC_POSTMASTER, REPLICATION_STANDBY,
+ gettext_noop("Specifies a file name whose presence ends recovery in the standby."),
+ NULL
+ },
+ &PromoteTriggerFile,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"primary_conninfo", PGC_POSTMASTER, REPLICATION_STANDBY,
+ gettext_noop("Sets the connection string to be used to connect to the sending server."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &PrimaryConnInfo,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"primary_slot_name", PGC_POSTMASTER, REPLICATION_STANDBY,
+ gettext_noop("Sets the name of the replication slot to use on the sending server."),
+ NULL
+ },
+ &PrimarySlotName,
+ "",
+ check_primary_slot_name, NULL, NULL
+ },
+
+ {
{"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE,
gettext_noop("Sets the client's character set encoding."),
NULL,
@@ -4072,6 +4236,16 @@ static struct config_enum ConfigureNamesEnum[] =
},
{
+ {"recovery_target_action", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the action to perform upon reaching the recovery target."),
+ NULL
+ },
+ &recoveryTargetAction,
+ RECOVERY_TARGET_ACTION_PAUSE, recovery_target_action_options,
+ NULL, NULL, NULL
+ },
+
+ {
{"trace_recovery_messages", PGC_SIGHUP, DEVELOPER_OPTIONS,
gettext_noop("Enables logging of recovery-related debugging information."),
gettext_noop("Each level includes all the levels that follow it. The later"
@@ -10838,4 +11012,251 @@ show_data_directory_mode(void)
return buf;
}
+static bool
+check_recovery_target_timeline(char **newval, void **extra, GucSource source)
+{
+ RecoveryTargetTimeLineGoal rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
+ RecoveryTargetTimeLineGoal *myextra;
+
+ if (strcmp(*newval, "") == 0)
+ rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
+ else if (strcmp(*newval, "latest") == 0)
+ rttg = RECOVERY_TARGET_TIMELINE_LATEST;
+ else
+ {
+ errno = 0;
+ strtoul(*newval, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ {
+ GUC_check_errdetail("recovery_target_timeline is not a valid number");
+ return false;
+ }
+ rttg = RECOVERY_TARGET_TIMELINE_NUMERIC;
+ }
+
+ myextra = (RecoveryTargetTimeLineGoal *) guc_malloc(ERROR, sizeof(RecoveryTargetTimeLineGoal));
+ *myextra = rttg;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+assign_recovery_target_timeline(const char *newval, void *extra)
+{
+ recoveryTargetTimeLineGoal = *((RecoveryTargetTimeLineGoal *) extra);
+ if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC)
+ recoveryTargetTLIRequested = (TimeLineID) strtoul(newval, NULL, 0);
+ else
+ recoveryTargetTLIRequested = 0;
+}
+
+static bool
+check_recovery_target(char **newval, void **extra, GucSource source)
+{
+ if (strcmp(*newval, "immediate") != 0 && strcmp(*newval, "") != 0)
+ {
+ GUC_check_errdetail("The only allowed value is \"immediate\".");
+ return false;
+ }
+ return true;
+}
+
+static void
+assign_recovery_target(const char *newval, void *extra)
+{
+ 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;
+}
+
+static bool
+check_recovery_target_xid(char **newval, void **extra, GucSource source)
+{
+ if (strcmp(*newval, "") != 0)
+ {
+ TransactionId xid;
+ TransactionId *myextra;
+
+ errno = 0;
+ xid = (TransactionId) strtoul(*newval, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ return false;
+
+ myextra = (TransactionId *) guc_malloc(ERROR, sizeof(TransactionId));
+ *myextra = xid;
+ *extra = (void *) myextra;
+ }
+ return true;
+}
+
+static void
+assign_recovery_target_xid(const char *newval, void *extra)
+{
+ if (newval && strcmp(newval, "") != 0)
+ {
+ recoveryTarget = RECOVERY_TARGET_XID;
+ recoveryTargetXid = *((TransactionId *) extra);
+ }
+ else
+ recoveryTarget = RECOVERY_TARGET_UNSET;
+}
+
+static bool
+check_recovery_target_time(char **newval, void **extra, GucSource source)
+{
+ if (strcmp(*newval, "") != 0)
+ {
+ TimestampTz time;
+ TimestampTz *myextra;
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ /* reject some special values */
+ if (strcmp(*newval, "epoch") == 0 ||
+ strcmp(*newval, "infinity") == 0 ||
+ strcmp(*newval, "-infinity") == 0 ||
+ strcmp(*newval, "now") == 0 ||
+ strcmp(*newval, "today") == 0 ||
+ strcmp(*newval, "tomorrow") == 0 ||
+ strcmp(*newval, "yesterday") == 0)
+ {
+ return false;
+ }
+
+ PG_TRY();
+ {
+ time = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
+ CStringGetDatum(*newval),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error message */
+ GUC_check_errdetail("%s", edata->message);
+ FreeErrorData(edata);
+ return false;
+ }
+ PG_END_TRY();
+
+ myextra = (TimestampTz *) guc_malloc(ERROR, sizeof(TimestampTz));
+ *myextra = time;
+ *extra = (void *) myextra;
+ }
+ return true;
+}
+
+static void
+assign_recovery_target_time(const char *newval, void *extra)
+{
+ if (newval && strcmp(newval, "") != 0)
+ {
+ recoveryTarget = RECOVERY_TARGET_TIME;
+ recoveryTargetTime = *((TimestampTz *) extra);
+ }
+ else
+ recoveryTarget = RECOVERY_TARGET_UNSET;
+}
+
+static bool
+check_recovery_target_name(char **newval, void **extra, GucSource source)
+{
+ /* Use the value of newval directly */
+ if (strlen(*newval) >= MAXFNAMELEN)
+ {
+ GUC_check_errdetail("recovery_target_name is too long (maximum %d characters)",
+ MAXFNAMELEN - 1);
+ return false;
+ }
+ return true;
+}
+
+static void
+assign_recovery_target_name(const char *newval, void *extra)
+{
+ if (newval && strcmp(newval, "") != 0)
+ {
+ recoveryTarget = RECOVERY_TARGET_NAME;
+ recoveryTargetName = (char *) newval;
+ }
+ else
+ recoveryTarget = RECOVERY_TARGET_UNSET;
+}
+
+static bool
+check_recovery_target_lsn(char **newval, void **extra, GucSource source)
+{
+ if (strcmp(*newval, "") != 0)
+ {
+ XLogRecPtr lsn;
+ XLogRecPtr *myextra;
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ /*
+ * Convert the LSN string given by the user to XLogRecPtr form.
+ */
+ PG_TRY();
+ {
+ lsn = DatumGetLSN(DirectFunctionCall3(pg_lsn_in,
+ CStringGetDatum(*newval),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error message */
+ GUC_check_errdetail("%s", edata->message);
+ FreeErrorData(edata);
+ return false;
+ }
+ PG_END_TRY();
+
+ myextra = (XLogRecPtr *) guc_malloc(ERROR, sizeof(XLogRecPtr));
+ *myextra = lsn;
+ *extra = (void *) myextra;
+ }
+ return true;
+}
+
+static void
+assign_recovery_target_lsn(const char *newval, void *extra)
+{
+ if (newval && strcmp(newval, "") != 0)
+ {
+ recoveryTarget = RECOVERY_TARGET_LSN;
+ recoveryTargetLSN = *((XLogRecPtr *) extra);
+ }
+ else
+ recoveryTarget = RECOVERY_TARGET_UNSET;
+}
+
+static bool
+check_primary_slot_name(char **newval, void **extra, GucSource source)
+{
+ if (*newval && strcmp(*newval, "") != 0 &&
+ !ReplicationSlotValidateName(*newval, WARNING))
+ return false;
+
+ return true;
+}
+
#include "guc-file.c"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3038fe627b2..ee9ec6a1200 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -228,6 +228,45 @@
#archive_timeout = 0 # force a logfile segment switch after this
# number of seconds; 0 disables
+# - Archive Recovery -
+
+# These are only used in recovery mode.
+
+#restore_command = '' # command to use to restore an archived logfile segment
+ # placeholders: %p = path of file to restore
+ # %f = file name only
+ # e.g. 'cp /mnt/server/archivedir/%f %p'
+ # (change requires restart)
+#archive_cleanup_command = '' # command to execute at every restartpoint
+ # (change requires restart)
+#recovery_end_command = '' # command to execute at completion of recovery
+ # (change requires restart)
+
+# - Recovery Target -
+
+# Set these only when performing a targeted recovery.
+
+#recovery_target = '' # 'immediate' to end recovery as soon as a
+ # consistent state is reached
+ # (change requires restart)
+#recovery_target_name = '' # the named restore point to which recovery will proceed
+ # (change requires restart)
+#recovery_target_time = '' # the time stamp up to which recovery will proceed
+ # (change requires restart)
+#recovery_target_xid = '' # the transaction ID up to which recovery will proceed
+ # (change requires restart)
+#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
+ # (change requires restart)
+#recovery_target_inclusive = on # Specifies whether to stop:
+ # just after the specified recovery target (on)
+ # just before the recovery target (off)
+ # (change requires restart)
+#recovery_target_timeline = '' # unset means read from control file (default),
+ # or set to 'latest' or timeline ID
+ # (change requires restart)
+#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
+ # (change requires restart)
+
#------------------------------------------------------------------------------
# REPLICATION
@@ -261,6 +300,12 @@
# These settings are ignored on a master server.
+#primary_conninfo = '' # connection string to sending server
+ # (change requires restart)
+#primary_slot_name = '' # replication slot on sending server
+ # (change requires restart)
+#promote_trigger_file = '' # file name whose presence ends recovery
+ # (change requires restart)
#hot_standby = on # "off" disallows queries during recovery
# (change requires restart)
#max_standby_archive_delay = 30s # max delay before canceling queries
@@ -278,6 +323,8 @@
# in milliseconds; 0 disables
#wal_retrieve_retry_interval = 5s # time to wait before retrying to
# retrieve WAL after a failed attempt
+#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery
+ # (change requires restart)
# - Subscribers -
diff --git a/src/bin/pg_archivecleanup/pg_archivecleanup.c b/src/bin/pg_archivecleanup/pg_archivecleanup.c
index 51904c6c2af..c62478dbd6f 100644
--- a/src/bin/pg_archivecleanup/pg_archivecleanup.c
+++ b/src/bin/pg_archivecleanup/pg_archivecleanup.c
@@ -265,7 +265,7 @@ usage(void)
printf(_(" -x EXT clean up files if they have this extension\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\n"
- "For use as archive_cleanup_command in recovery.conf when standby_mode = on:\n"
+ "For use as archive_cleanup_command in postgresql.conf:\n"
" archive_cleanup_command = 'pg_archivecleanup [OPTION]... ARCHIVELOCATION %%r'\n"
"e.g.\n"
" archive_cleanup_command = 'pg_archivecleanup /mnt/server/archiverdir %%r'\n"));
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index d6fef38760e..af1c4de58f9 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -131,7 +131,7 @@ static int has_xlogendptr = 0;
static volatile LONG has_xlogendptr = 0;
#endif
-/* Contents of recovery.conf to be generated */
+/* Contents of configuration file to be generated */
static PQExpBuffer recoveryconfcontents = NULL;
/* Function headers */
@@ -346,7 +346,7 @@ usage(void)
printf(_(" -r, --max-rate=RATE maximum transfer rate to transfer data directory\n"
" (in kB/s, or use suffix \"k\" or \"M\")\n"));
printf(_(" -R, --write-recovery-conf\n"
- " write recovery.conf for replication\n"));
+ " write configuration for replication\n"));
printf(_(" -T, --tablespace-mapping=OLDDIR=NEWDIR\n"
" relocate tablespace in OLDDIR to NEWDIR\n"));
printf(_(" --waldir=WALDIR location for the write-ahead log directory\n"));
@@ -974,6 +974,9 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
bool basetablespace = PQgetisnull(res, rownum, 0);
bool in_tarhdr = true;
bool skip_file = false;
+ bool is_postgresql_auto_conf = false;
+ bool found_postgresql_auto_conf = false;
+ int file_padding_len = 0;
size_t tarhdrsz = 0;
pgoff_t filesz = 0;
@@ -1113,8 +1116,8 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
{
/*
* End of chunk. If requested, and this is the base tablespace,
- * write recovery.conf into the tarfile. When done, close the file
- * (but not stdout).
+ * write configuration file into the tarfile. When done, close the
+ * file (but not stdout).
*
* Also, write two completely empty blocks at the end of the tar
* file, as required by some tar programs.
@@ -1126,19 +1129,31 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
if (basetablespace && writerecoveryconf)
{
char header[512];
- int padding;
- tarCreateHeader(header, "recovery.conf", NULL,
- recoveryconfcontents->len,
+ if (!found_postgresql_auto_conf)
+ {
+ int padding;
+
+ tarCreateHeader(header, "postgresql.auto.conf", NULL,
+ recoveryconfcontents->len,
+ pg_file_create_mode, 04000, 02000,
+ time(NULL));
+
+ padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
+
+ WRITE_TAR_DATA(header, sizeof(header));
+ WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len);
+ if (padding)
+ WRITE_TAR_DATA(zerobuf, padding);
+ }
+
+ tarCreateHeader(header, "standby.signal", NULL,
+ 0, /* zero-length file */
pg_file_create_mode, 04000, 02000,
time(NULL));
- padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len;
-
WRITE_TAR_DATA(header, sizeof(header));
- WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len);
- if (padding)
- WRITE_TAR_DATA(zerobuf, padding);
+ WRITE_TAR_DATA(zerobuf, 511);
}
/* 2 * 512 bytes empty data at end of file */
@@ -1182,8 +1197,8 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
if (!writerecoveryconf || !basetablespace)
{
/*
- * When not writing recovery.conf, or when not working on the base
- * tablespace, we never have to look for an existing recovery.conf
+ * When not writing config file, or when not working on the base
+ * tablespace, we never have to look for an existing configuration
* file in the stream.
*/
WRITE_TAR_DATA(copybuf, r);
@@ -1191,7 +1206,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
else
{
/*
- * Look for a recovery.conf in the existing tar stream. If it's
+ * Look for a config file in the existing tar stream. If it's
* there, we must skip it so we can later overwrite it with our
* own version of the file.
*
@@ -1235,29 +1250,46 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
{
/*
* We have the complete header structure in tarhdr,
- * look at the file metadata: - the subsequent file
- * contents have to be skipped if the filename is
- * recovery.conf - find out the size of the file
- * padded to the next multiple of 512
+ * look at the file metadata: we may want append
+ * recovery info into postgresql.auto.conf and skip
+ * standby.signal file. In both cases we must
+ * calculate tar padding
*/
- int padding;
-
- skip_file = (strcmp(&tarhdr[0], "recovery.conf") == 0);
+ skip_file = (strcmp(&tarhdr[0], "standby.signal") == 0);
+ is_postgresql_auto_conf = (strcmp(&tarhdr[0], "postgresql.auto.conf") == 0);
filesz = read_tar_number(&tarhdr[124], 12);
+ file_padding_len = ((filesz + 511) & ~511) - filesz;
+
+ if (is_postgresql_auto_conf && writerecoveryconf)
+ {
+ /* replace tar header */
+ char header[512];
- padding = ((filesz + 511) & ~511) - filesz;
- filesz += padding;
+ tarCreateHeader(header, "postgresql.auto.conf", NULL,
+ filesz + recoveryconfcontents->len,
+ pg_file_create_mode, 04000, 02000,
+ time(NULL));
+
+ WRITE_TAR_DATA(header, sizeof(header));
+ }
+ else
+ {
+ /* copy stream with padding */
+ filesz += file_padding_len;
+
+ if (!skip_file)
+ {
+ /*
+ * If we're not skipping the file, write the
+ * tar header unmodified.
+ */
+ WRITE_TAR_DATA(tarhdr, 512);
+ }
+ }
/* Next part is the file, not the header */
in_tarhdr = false;
-
- /*
- * If we're not skipping the file, write the tar
- * header unmodified.
- */
- if (!skip_file)
- WRITE_TAR_DATA(tarhdr, 512);
}
}
else
@@ -1281,6 +1313,32 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
pos += bytes2write;
filesz -= bytes2write;
}
+ else if (is_postgresql_auto_conf && writerecoveryconf)
+ {
+ /* append recovery config to postgresql.auto.conf */
+ int padding;
+ int tailsize;
+
+ tailsize = (512 - file_padding_len) + recoveryconfcontents->len;
+ padding = ((tailsize + 511) & ~511) - tailsize;
+
+ WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len);
+
+ if (padding)
+ {
+ char zerobuf[512];
+
+ MemSet(zerobuf, 0, sizeof(zerobuf));
+ WRITE_TAR_DATA(zerobuf, padding);
+ }
+
+ /* skip original file padding */
+ is_postgresql_auto_conf = false;
+ skip_file = true;
+ filesz += file_padding_len;
+
+ found_postgresql_auto_conf = true;
+ }
else
{
/*
@@ -1289,6 +1347,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
*/
in_tarhdr = true;
skip_file = false;
+ is_postgresql_auto_conf = false;
tarhdrsz = 0;
filesz = 0;
}
@@ -1614,7 +1673,7 @@ escape_quotes(const char *src)
}
/*
- * Create a recovery.conf file in memory using a PQExpBuffer
+ * Create a configuration file in memory using a PQExpBuffer
*/
static void
GenerateRecoveryConf(PGconn *conn)
@@ -1638,8 +1697,6 @@ GenerateRecoveryConf(PGconn *conn)
disconnect_and_exit(1);
}
- appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n");
-
initPQExpBuffer(&conninfo_buf);
for (option = connOptions; option && option->keyword; option++)
{
@@ -1698,8 +1755,9 @@ GenerateRecoveryConf(PGconn *conn)
/*
- * Write a recovery.conf file into the directory specified in basedir,
+ * Write the configuration file into the directory specified in basedir,
* with the contents already collected in memory.
+ * Then write the signal file into the basedir also.
*/
static void
WriteRecoveryConf(void)
@@ -1707,12 +1765,12 @@ WriteRecoveryConf(void)
char filename[MAXPGPATH];
FILE *cf;
- sprintf(filename, "%s/recovery.conf", basedir);
+ snprintf(filename, MAXPGPATH, "%s/%s", basedir, "postgresql.auto.conf");
- cf = fopen(filename, "w");
+ cf = fopen(filename, "a");
if (cf == NULL)
{
- fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), progname, filename, strerror(errno));
+ fprintf(stderr, _("%s: could not open file \"%s\": %s\n"), progname, filename, strerror(errno));
disconnect_and_exit(1);
}
@@ -1725,6 +1783,16 @@ WriteRecoveryConf(void)
}
fclose(cf);
+
+ snprintf(filename, MAXPGPATH, "%s/%s", basedir, "standby.signal");
+ cf = fopen(filename, "w");
+ if (cf == NULL)
+ {
+ fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+
+ fclose(cf);
}
@@ -1780,7 +1848,7 @@ BaseBackup(void)
}
/*
- * Build contents of recovery.conf if requested
+ * Build contents of configuration file if requested
*/
if (writerecoveryconf)
GenerateRecoveryConf(conn);
@@ -2094,7 +2162,7 @@ BaseBackup(void)
#endif
}
- /* Free the recovery.conf contents */
+ /* Free the configuration file contents */
destroyPQExpBuffer(recoveryconfcontents);
/*
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 2211d90c6f9..3e1c3863c4f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -358,19 +358,16 @@ SKIP:
$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backupR", '-R' ],
'pg_basebackup -R runs');
-ok(-f "$tempdir/backupR/recovery.conf", 'recovery.conf was created');
-my $recovery_conf = slurp_file "$tempdir/backupR/recovery.conf";
+ok(-f "$tempdir/backupR/postgresql.auto.conf", 'postgresql.auto.conf exists');
+ok(-f "$tempdir/backupR/standby.signal", 'standby.signal was created');
+my $recovery_conf = slurp_file "$tempdir/backupR/postgresql.auto.conf";
rmtree("$tempdir/backupR");
my $port = $node->port;
like(
$recovery_conf,
- qr/^standby_mode = 'on'\n/m,
- 'recovery.conf sets standby_mode');
-like(
- $recovery_conf,
qr/^primary_conninfo = '.*port=$port.*'\n/m,
- 'recovery.conf sets primary_conninfo');
+ 'postgresql.auto.conf sets primary_conninfo');
$node->command_ok(
[ 'pg_basebackup', '-D', "$tempdir/backupxd" ],
@@ -478,9 +475,9 @@ $node->command_ok(
],
'pg_basebackup with replication slot and -R runs');
like(
- slurp_file("$tempdir/backupxs_sl_R/recovery.conf"),
+ slurp_file("$tempdir/backupxs_sl_R/postgresql.auto.conf"),
qr/^primary_slot_name = 'slot1'\n/m,
- 'recovery.conf sets primary_slot_name');
+ 'recovery conf file sets primary_slot_name');
my $checksum = $node->safe_psql('postgres', 'SHOW data_checksums;');
is($checksum, 'on', 'checksums are enabled');
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 1dce56d0352..3d07da5d949 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -159,12 +159,13 @@ sub create_standby
my $connstr_master = $node_master->connstr();
$node_standby->append_conf(
- "recovery.conf", qq(
+ "postgresql.conf", qq(
primary_conninfo='$connstr_master application_name=rewind_standby'
-standby_mode=on
recovery_target_timeline='latest'
));
+ $node_standby->set_standby_mode();
+
# Start standby
$node_standby->start;
@@ -270,12 +271,13 @@ sub run_pg_rewind
# Plug-in rewound node to the now-promoted standby node
my $port_standby = $node_standby->port;
$node_master->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
primary_conninfo='port=$port_standby'
-standby_mode=on
recovery_target_timeline='latest'
));
+ $node_master->set_standby_mode();
+
# Restart the master to check that rewind went correctly
$node_master->start;
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index e01d12eb7c8..f3a7ba4d421 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -75,7 +75,7 @@ extern HotStandbyState standbyState;
/*
* Recovery target type.
- * Only set during a Point in Time recovery, not when standby_mode = on
+ * Only set during a Point in Time recovery, not when in standby mode.
*/
typedef enum
{
@@ -87,6 +87,16 @@ typedef enum
RECOVERY_TARGET_IMMEDIATE
} RecoveryTargetType;
+/*
+ * Recovery target TimeLine goal
+ */
+typedef enum
+{
+ RECOVERY_TARGET_TIMELINE_CONTROLFILE,
+ RECOVERY_TARGET_TIMELINE_LATEST,
+ RECOVERY_TARGET_TIMELINE_NUMERIC
+} RecoveryTargetTimeLineGoal;
+
extern XLogRecPtr ProcLastRecPtr;
extern XLogRecPtr XactLastRecEnd;
extern PGDLLIMPORT XLogRecPtr XactLastCommitEnd;
@@ -109,9 +119,32 @@ extern bool wal_compression;
extern bool *wal_consistency_checking;
extern char *wal_consistency_checking_string;
extern bool log_checkpoints;
+extern char *recoveryRestoreCommand;
+extern char *recoveryEndCommand;
+extern char *archiveCleanupCommand;
+extern bool recoveryTargetInclusive;
+extern int recoveryTargetAction;
+extern int recovery_min_apply_delay;
+extern char *PrimaryConnInfo;
+extern char *PrimarySlotName;
+
+/* indirectly set via GUC system */
+extern TransactionId recoveryTargetXid;
+extern TimestampTz recoveryTargetTime;
+extern char *recoveryTargetName;
+extern XLogRecPtr recoveryTargetLSN;
+extern RecoveryTargetType recoveryTarget;
+extern char *PromoteTriggerFile;
+extern RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal;
+extern TimeLineID recoveryTargetTLIRequested;
+extern TimeLineID recoveryTargetTLI;
extern int CheckPointSegments;
+/* option set locally in startup process only when signal files exist */
+extern bool StandbyModeRequested;
+extern bool StandbyMode;
+
/* Archive modes */
typedef enum ArchiveMode
{
@@ -319,8 +352,8 @@ extern void do_pg_abort_backup(void);
extern SessionBackupState get_backup_status(void);
/* File path names (all relative to $PGDATA) */
-#define RECOVERY_COMMAND_FILE "recovery.conf"
-#define RECOVERY_COMMAND_DONE "recovery.done"
+#define RECOVERY_SIGNAL_FILE "recovery.signal"
+#define STANDBY_SIGNAL_FILE "standby.signal"
#define BACKUP_LABEL_FILE "backup_label"
#define BACKUP_LABEL_OLD "backup_label.old"
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 668d9efd357..6f9fdb6a5fb 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -69,6 +69,8 @@ enum config_group
WAL_SETTINGS,
WAL_CHECKPOINTS,
WAL_ARCHIVING,
+ WAL_ARCHIVE_RECOVERY,
+ WAL_RECOVERY_TARGET,
REPLICATION,
REPLICATION_SENDING,
REPLICATION_MASTER,
diff --git a/src/port/quotes.c b/src/port/quotes.c
index 29770c7a00e..94b63b9bb64 100644
--- a/src/port/quotes.c
+++ b/src/port/quotes.c
@@ -19,7 +19,7 @@
* Escape (by doubling) any single quotes or backslashes in given string
*
* Note: this is used to process postgresql.conf entries and to quote
- * string literals in pg_basebackup for creating recovery.conf.
+ * string literals in pg_basebackup for writing the recovery configuration.
* Since postgresql.conf strings are defined to treat backslashes as escapes,
* we have to double backslashes here.
*
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index efdebc3877c..8a2c6fc1221 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -635,8 +635,6 @@ of a backup previously created on that node with $node->backup.
Does not start the node after initializing it.
-A recovery.conf is not created.
-
Streaming replication can be enabled on this node by passing the keyword
parameter has_streaming => 1. This is disabled by default.
@@ -834,10 +832,10 @@ sub enable_streaming
print "### Enabling streaming replication for node \"$name\"\n";
$self->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
primary_conninfo='$root_connstr application_name=$name'
-standby_mode=on
));
+ $self->set_standby_mode();
return;
}
@@ -863,10 +861,26 @@ sub enable_restoring
: qq{cp "$path/%f" "%p"};
$self->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
restore_command = '$copy_command'
-standby_mode = on
));
+ $self->set_standby_mode();
+ return;
+}
+
+=pod
+
+=item $node->set_standby_mode()
+
+Place standby.signal file.
+
+=cut
+
+sub set_standby_mode
+{
+ my ($self) = @_;
+
+ $self->append_conf('standby.signal', '');
return;
}
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 8dff5fc7202..beb45551a24 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -131,7 +131,7 @@ is( $node_master->psql(
qq[SELECT pg_create_physical_replication_slot('$slotname_1');]),
0,
'physical slot created on master');
-$node_standby_1->append_conf('recovery.conf',
+$node_standby_1->append_conf('postgresql.conf',
"primary_slot_name = $slotname_1");
$node_standby_1->append_conf('postgresql.conf',
"wal_receiver_status_interval = 1");
@@ -142,7 +142,7 @@ is( $node_standby_1->psql(
qq[SELECT pg_create_physical_replication_slot('$slotname_2');]),
0,
'physical slot created on intermediate replica');
-$node_standby_2->append_conf('recovery.conf',
+$node_standby_2->append_conf('postgresql.conf',
"primary_slot_name = $slotname_2");
$node_standby_2->append_conf('postgresql.conf',
"wal_receiver_status_interval = 1");
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index e867479f206..f6f2e8b745b 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -23,7 +23,7 @@ sub test_recovery_standby
foreach my $param_item (@$recovery_params)
{
- $node_standby->append_conf('recovery.conf', qq($param_item));
+ $node_standby->append_conf('postgresql.conf', qq($param_item));
}
$node_standby->start;
diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index a7ccb7b4a3b..79cbffb827b 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -47,12 +47,10 @@ $node_standby_1->psql('postgres', "SELECT pg_promote(wait_seconds => 300)",
is($psql_out, 't', "promotion of standby with pg_promote");
# Switch standby 2 to replay from standby 1
-rmtree($node_standby_2->data_dir . '/recovery.conf');
my $connstr_1 = $node_standby_1->connstr;
$node_standby_2->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
primary_conninfo='$connstr_1 application_name=@{[$node_standby_2->name]}'
-standby_mode=on
recovery_target_timeline='latest'
));
$node_standby_2->restart;
diff --git a/src/test/recovery/t/005_replay_delay.pl b/src/test/recovery/t/005_replay_delay.pl
index 8909c4548bf..6c85c928c10 100644
--- a/src/test/recovery/t/005_replay_delay.pl
+++ b/src/test/recovery/t/005_replay_delay.pl
@@ -25,7 +25,7 @@ my $delay = 3;
$node_standby->init_from_backup($node_master, $backup_name,
has_streaming => 1);
$node_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_min_apply_delay = '${delay}s'
));
$node_standby->start;
diff --git a/src/test/recovery/t/009_twophase.pl b/src/test/recovery/t/009_twophase.pl
index 9ea3bd65fc1..dac2d4ec0d6 100644
--- a/src/test/recovery/t/009_twophase.pl
+++ b/src/test/recovery/t/009_twophase.pl
@@ -230,7 +230,7 @@ is($psql_rc, '0', "Restore of prepared transaction on promoted standby");
# restart old master as new standby
$cur_standby->enable_streaming($cur_master);
$cur_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_target_timeline='latest'
));
$cur_standby->start;
@@ -268,7 +268,7 @@ is($psql_out, '1',
# restart old master as new standby
$cur_standby->enable_streaming($cur_master);
$cur_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_target_timeline='latest'
));
$cur_standby->start;
@@ -308,7 +308,7 @@ is($psql_out, '1',
# restart old master as new standby
$cur_standby->enable_streaming($cur_master);
$cur_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_target_timeline='latest'
));
$cur_standby->start;
diff --git a/src/test/recovery/t/010_logical_decoding_timelines.pl b/src/test/recovery/t/010_logical_decoding_timelines.pl
index a76eea86a5a..4fbd386332a 100644
--- a/src/test/recovery/t/010_logical_decoding_timelines.pl
+++ b/src/test/recovery/t/010_logical_decoding_timelines.pl
@@ -76,7 +76,7 @@ $node_replica->init_from_backup(
$node_master, $backup_name,
has_streaming => 1,
has_restoring => 1);
-$node_replica->append_conf('recovery.conf',
+$node_replica->append_conf('postgresql.conf',
q[primary_slot_name = 'phys_slot']);
$node_replica->start;
diff --git a/src/test/recovery/t/012_subtransactions.pl b/src/test/recovery/t/012_subtransactions.pl
index efc23d05598..e26cc9c2ce1 100644
--- a/src/test/recovery/t/012_subtransactions.pl
+++ b/src/test/recovery/t/012_subtransactions.pl
@@ -120,7 +120,7 @@ is($psql_out, '8128', "Visible");
($node_master, $node_standby) = ($node_standby, $node_master);
$node_standby->enable_streaming($node_master);
$node_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_target_timeline='latest'
));
$node_standby->start;
@@ -171,7 +171,7 @@ is($psql_out, '-1', "Not visible");
($node_master, $node_standby) = ($node_standby, $node_master);
$node_standby->enable_streaming($node_master);
$node_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_target_timeline='latest'
));
$node_standby->start;
@@ -212,7 +212,7 @@ is($psql_out, '-1', "Not visible");
($node_master, $node_standby) = ($node_standby, $node_master);
$node_standby->enable_streaming($node_master);
$node_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_target_timeline='latest'
));
$node_standby->start;