diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
index 26af221..e131b7d 100644
--- a/doc/src/sgml/recovery-config.sgml
+++ b/doc/src/sgml/recovery-config.sgml
@@ -232,6 +232,21 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
+
+
+ recovery_target_lsn (pg_lsn)
+
+ recovery_target_lsn> recovery parameter
+
+
+
+
+ This parameter specifies the LSN of the write-ahead log stream up to
+ which recovery will proceed. The precise stopping point is also
+ influenced by .
+
+
+
diff --git a/src/backend/access/transam/recovery.conf.sample b/src/backend/access/transam/recovery.conf.sample
index b777400..32f6903 100644
--- a/src/backend/access/transam/recovery.conf.sample
+++ b/src/backend/access/transam/recovery.conf.sample
@@ -67,7 +67,7 @@
# must set a recovery target.
#
# You may set a recovery target either by transactionId, by name,
-# or by timestamp. Recovery may either include or exclude the
+# by timestamp or by LSN. Recovery may either include or exclude the
# transaction(s) with the recovery target value (ie, stop either
# just after or just before the given target, respectively).
#
@@ -78,6 +78,8 @@
#
#recovery_target_xid = ''
#
+#recovery_target_lsn = '' # e.g. '0/70006B8'
+#
#recovery_target_inclusive = true
#
#
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b473f19..05abbfc 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -67,6 +67,7 @@
#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"
@@ -254,6 +255,7 @@ 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;
@@ -275,6 +277,7 @@ static bool fast_promote = false;
*/
static TransactionId recoveryStopXid;
static TimestampTz recoveryStopTime;
+static XLogRecPtr recoveryStopLSN;
static char recoveryStopName[MAXFNAMELEN];
static bool recoveryStopAfter;
@@ -5080,6 +5083,23 @@ readRecoveryCommandFile(void)
(errmsg_internal("recovery_target_name = '%s'",
recoveryTargetName)));
}
+ else if (strcmp(item->name, "recovery_target_lsn") == 0)
+ {
+ recoveryTarget = RECOVERY_TARGET_LSN;
+
+ /*
+ * 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)
@@ -5390,11 +5410,29 @@ recoveryStopsBefore(XLogReaderState *record)
recoveryStopAfter = false;
recoveryStopXid = InvalidTransactionId;
+ recoveryStopLSN = InvalidXLogRecPtr;
recoveryStopTime = 0;
recoveryStopName[0] = '\0';
return true;
}
+ /* Check if target LSN has been reached */
+ if (recoveryTarget == RECOVERY_TARGET_LSN &&
+ !recoveryTargetInclusive &&
+ record->ReadRecPtr >= recoveryTargetLSN)
+ {
+ recoveryStopAfter = false;
+ recoveryStopXid = InvalidTransactionId;
+ recoveryStopLSN = record->ReadRecPtr;
+ recoveryStopTime = 0;
+ recoveryStopName[0] = '\0';
+ ereport(LOG,
+ (errmsg("recovery stopping before LSN \"%X/%X\"",
+ (uint32) (recoveryStopLSN >> 32),
+ (uint32) recoveryStopLSN)));
+ return true;
+ }
+
/* Otherwise we only consider stopping before COMMIT or ABORT records. */
if (XLogRecGetRmid(record) != RM_XACT_ID)
return false;
@@ -5469,6 +5507,7 @@ recoveryStopsBefore(XLogReaderState *record)
recoveryStopAfter = false;
recoveryStopXid = recordXid;
recoveryStopTime = recordXtime;
+ recoveryStopLSN = InvalidXLogRecPtr;
recoveryStopName[0] = '\0';
if (isCommit)
@@ -5522,6 +5561,7 @@ recoveryStopsAfter(XLogReaderState *record)
{
recoveryStopAfter = true;
recoveryStopXid = InvalidTransactionId;
+ recoveryStopLSN = InvalidXLogRecPtr;
(void) getRecordTimestamp(record, &recoveryStopTime);
strlcpy(recoveryStopName, recordRestorePointData->rp_name, MAXFNAMELEN);
@@ -5533,6 +5573,23 @@ recoveryStopsAfter(XLogReaderState *record)
}
}
+ /* Check if the target LSN has been reached */
+ if (recoveryTarget == RECOVERY_TARGET_LSN &&
+ recoveryTargetInclusive &&
+ record->ReadRecPtr >= recoveryTargetLSN)
+ {
+ recoveryStopAfter = true;
+ recoveryStopXid = InvalidTransactionId;
+ recoveryStopLSN = record->ReadRecPtr;
+ recoveryStopTime = 0;
+ recoveryStopName[0] = '\0';
+ ereport(LOG,
+ (errmsg("recovery stopping after LSN \"%X/%X\"",
+ (uint32) (recoveryStopLSN >> 32),
+ (uint32) recoveryStopLSN)));
+ return true;
+ }
+
if (rmid != RM_XACT_ID)
return false;
@@ -5588,6 +5645,7 @@ recoveryStopsAfter(XLogReaderState *record)
recoveryStopAfter = true;
recoveryStopXid = recordXid;
recoveryStopTime = recordXtime;
+ recoveryStopLSN = InvalidXLogRecPtr;
recoveryStopName[0] = '\0';
if (xact_info == XLOG_XACT_COMMIT ||
@@ -5619,6 +5677,7 @@ recoveryStopsAfter(XLogReaderState *record)
recoveryStopAfter = true;
recoveryStopXid = InvalidTransactionId;
recoveryStopTime = 0;
+ recoveryStopLSN = InvalidXLogRecPtr;
recoveryStopName[0] = '\0';
return true;
}
@@ -6045,6 +6104,11 @@ StartupXLOG(void)
ereport(LOG,
(errmsg("starting point-in-time recovery to \"%s\"",
recoveryTargetName)));
+ else if (recoveryTarget == RECOVERY_TARGET_LSN)
+ ereport(LOG,
+ (errmsg("starting point-in-time recovery to LSN \"%X/%X\"",
+ (uint32) (recoveryTargetLSN >> 32),
+ (uint32) recoveryTargetLSN)));
else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
ereport(LOG,
(errmsg("starting point-in-time recovery to earliest consistent point")));
@@ -7114,6 +7178,11 @@ StartupXLOG(void)
"%s %s\n",
recoveryStopAfter ? "after" : "before",
timestamptz_to_str(recoveryStopTime));
+ else if (recoveryTarget == RECOVERY_TARGET_LSN)
+ snprintf(reason, sizeof(reason),
+ "at LSN %X/%X\n",
+ (uint32 ) (recoveryStopLSN >> 32),
+ (uint32) recoveryStopLSN);
else if (recoveryTarget == RECOVERY_TARGET_NAME)
snprintf(reason, sizeof(reason),
"at restore point \"%s\"",
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index e7e91fc..9eb5ca8 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -83,6 +83,7 @@ typedef enum
RECOVERY_TARGET_XID,
RECOVERY_TARGET_TIME,
RECOVERY_TARGET_NAME,
+ RECOVERY_TARGET_LSN,
RECOVERY_TARGET_IMMEDIATE
} RecoveryTargetType;
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index b20116a..8945a6c 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 => 7;
+use Test::More tests => 9;
# Create and test a standby from given backup, with a certain
# recovery target.
@@ -86,6 +86,16 @@ my $lsn4 =
$node_master->safe_psql('postgres',
"SELECT pg_create_restore_point('$recovery_name');");
+# And now for a recovery target LSN
+$node_master->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(4001,5000))");
+my $recovery_lsn = $node_master->safe_psql('postgres', "SELECT pg_current_xlog_location()");
+my $lsn5 =
+ $node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
+
+$node_master->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(5001,6000))");
+
# Force archiving of WAL file
$node_master->safe_psql('postgres', "SELECT pg_switch_xlog()");
@@ -102,6 +112,9 @@ test_recovery_standby('Time', 'standby_3', $node_master, \@recovery_params,
@recovery_params = ("recovery_target_name = '$recovery_name'");
test_recovery_standby('Name', 'standby_4', $node_master, \@recovery_params,
"4000", $lsn4);
+@recovery_params = ("recovery_target_lsn = '$recovery_lsn'");
+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
@@ -111,16 +124,23 @@ test_recovery_standby('Name', 'standby_4', $node_master, \@recovery_params,
"recovery_target_xid = '$recovery_txid'",
"recovery_target_time = '$recovery_time'");
test_recovery_standby('Name + XID + Time',
- 'standby_5', $node_master, \@recovery_params, "3000", $lsn3);
+ '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_6', $node_master, \@recovery_params, "2000", $lsn2);
+ '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_7', $node_master, \@recovery_params, "4000", $lsn4);
+ '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);