On Tue, Aug 23, 2016 at 12:49 AM, Robert Haas <robertmh...@gmail.com> wrote:
> On Mon, Aug 22, 2016 at 8:28 AM, Michael Paquier
> <michael.paqu...@gmail.com> wrote:
>> On Mon, Aug 22, 2016 at 7:12 PM, Adrien Nayrat <adrien.nay...@dalibo.com> 
>> wrote:
>>> As Julien said, there is nothing to notice that error comes from
>>> recovery.conf.
>>> My fear would be that an user encounters an error like this. Il will be
>>> difficult to link to the recovery.conf.
>>
>> Thinking a bit wider than that, we may want to know such context for
>> normal GUC parameters as well, and that's not the case now. Perhaps
>> there is actually a reason why that's not done for GUCs, but it seems
>> that it would be useful there as well. That would give another reason
>> to move all that under the GUC umbrella.
>
> Maybe so, but that's been tried multiple times without success.  If
> you think an error context is useful here, and I bet it is, I'd say
> just add it and be done with it.

This has finished by being less ugly than I thought, so I implemented
it as attached. Patch 0001 introduces recovery_target_lsn, and patch
0002 sets up an error context callback generating things like that on
failure:
FATAL:  invalid input syntax for type pg_lsn: "popo"
CONTEXT:  line 11 of configuration file "recovery.conf", parameter
"recovery_target_lsn"
-- 
Michael
From 6f73e9b02ee9806dd73571e1190983748fcad03d Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@otacoo.com>
Date: Tue, 23 Aug 2016 16:12:03 +0900
Subject: [PATCH 1/2] Introduce recovery_target_lsn in recovery.conf

This new recovery target can be used to stop recovery once a given WAL
position is reached (LSN). Tests and documentation are added.
---
 doc/src/sgml/recovery-config.sgml               | 24 +++++++--
 src/backend/access/transam/recovery.conf.sample |  6 ++-
 src/backend/access/transam/xlog.c               | 69 +++++++++++++++++++++++++
 src/include/access/xlog.h                       |  1 +
 src/test/recovery/t/003_recovery_targets.pl     | 28 ++++++++--
 5 files changed, 119 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/recovery-config.sgml 
b/doc/src/sgml/recovery-config.sgml
index 26af221..ad977d3 100644
--- a/doc/src/sgml/recovery-config.sgml
+++ b/doc/src/sgml/recovery-config.sgml
@@ -157,9 +157,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' 
 # Windows
       By default, recovery will recover to the end of the WAL log. The
       following parameters can be used to specify an earlier stopping point.
       At most one of <varname>recovery_target</>,
-      <varname>recovery_target_name</>, <varname>recovery_target_time</>, or
-      <varname>recovery_target_xid</> can be used; if more than one of these
-      is specified in the configuration file, the last entry will be used.
+      <varname>recovery_target_lsn</>, <varname>recovery_target_name</>,
+      <varname>recovery_target_time</>, or <varname>recovery_target_xid</>
+      can be used; if more than one of these is specified in the configuration
+      file, the last entry will be used.
      </para>
 
      <variablelist>
@@ -232,6 +233,23 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' 
 # Windows
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry id="recovery-target-lsn" xreflabel="recovery_target_lsn">
+      <term><varname>recovery_target_lsn</varname> (<type>pg_lsn</type>)
+      <indexterm>
+        <primary><varname>recovery_target_lsn</> recovery parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        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 <xref linkend="recovery-target-inclusive">. This
+        parameter is parsed using the system data type
+        <link linkend="datatype-pg-lsn"><type>pg_lsn</></link>.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
 
      <para>
diff --git a/src/backend/access/transam/recovery.conf.sample 
b/src/backend/access/transam/recovery.conf.sample
index b777400..7a16751 100644
--- a/src/backend/access/transam/recovery.conf.sample
+++ b/src/backend/access/transam/recovery.conf.sample
@@ -67,8 +67,8 @@
 # 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
-# transaction(s) with the recovery target value (ie, stop either
+# by timestamp or by WAL position (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 f13f9c1..e3985ee 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;
 
@@ -5078,6 +5081,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)
@@ -5388,11 +5408,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 WAL position 
(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;
@@ -5467,6 +5505,7 @@ recoveryStopsBefore(XLogReaderState *record)
                recoveryStopAfter = false;
                recoveryStopXid = recordXid;
                recoveryStopTime = recordXtime;
+               recoveryStopLSN = InvalidXLogRecPtr;
                recoveryStopName[0] = '\0';
 
                if (isCommit)
@@ -5520,6 +5559,7 @@ recoveryStopsAfter(XLogReaderState *record)
                {
                        recoveryStopAfter = true;
                        recoveryStopXid = InvalidTransactionId;
+                       recoveryStopLSN = InvalidXLogRecPtr;
                        (void) getRecordTimestamp(record, &recoveryStopTime);
                        strlcpy(recoveryStopName, 
recordRestorePointData->rp_name, MAXFNAMELEN);
 
@@ -5531,6 +5571,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 WAL position 
(LSN) \"%X/%X\"",
+                                               (uint32) (recoveryStopLSN >> 
32),
+                                               (uint32) recoveryStopLSN)));
+               return true;
+       }
+
        if (rmid != RM_XACT_ID)
                return false;
 
@@ -5586,6 +5643,7 @@ recoveryStopsAfter(XLogReaderState *record)
                        recoveryStopAfter = true;
                        recoveryStopXid = recordXid;
                        recoveryStopTime = recordXtime;
+                       recoveryStopLSN = InvalidXLogRecPtr;
                        recoveryStopName[0] = '\0';
 
                        if (xact_info == XLOG_XACT_COMMIT ||
@@ -5617,6 +5675,7 @@ recoveryStopsAfter(XLogReaderState *record)
                recoveryStopAfter = true;
                recoveryStopXid = InvalidTransactionId;
                recoveryStopTime = 0;
+               recoveryStopLSN = InvalidXLogRecPtr;
                recoveryStopName[0] = '\0';
                return true;
        }
@@ -6043,6 +6102,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 WAL position (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")));
@@ -7112,6 +7176,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 14b7f7f..c9f332c 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 d1f6d78..a82545b 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);
-- 
2.9.3

From 82a591ca8be457f8be4f13080ededc5daabf3670 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@otacoo.com>
Date: Tue, 23 Aug 2016 16:28:05 +0900
Subject: [PATCH 2/2] Introduce error callback when parsing recovery.conf

At the moment an error occurs when recovery.conf is parsed, generate
a context message related to the parameter used, as follows:
CONTEXT:  line 11 of configuration file "recovery.conf", parameter foo

This is useful to understand from where a parsing failure is coming in
recovery.conf, particularly for parameters like recovery_target_time and
recovery_target_lsn that do not have recovery-specific error messages.
---
 src/backend/access/transam/xlog.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/backend/access/transam/xlog.c 
b/src/backend/access/transam/xlog.c
index e3985ee..9097841 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4935,6 +4935,20 @@ str_time(pg_time_t tnow)
 }
 
 /*
+ * error context callback for parsing of recovery.conf
+ *
+ * The argument for the error context must be of type ConfigVariable.
+ */
+static void
+readRecoveryErrorCallback(void *arg)
+{
+       ConfigVariable   *item = (ConfigVariable *) arg;
+
+       errcontext("line %d of configuration file \"%s\", parameter \"%s\"",
+                          item->sourceline, item->filename, item->name);
+}
+
+/*
  * See if there is a recovery command file (recovery.conf), and if so
  * read in parameters for archive recovery and XLOG streaming.
  *
@@ -4950,6 +4964,7 @@ readRecoveryCommandFile(void)
                           *head = NULL,
                           *tail = NULL;
        bool            recoveryTargetActionSet = false;
+       ErrorContextCallback errcallback;
 
 
        fd = AllocateFile(RECOVERY_COMMAND_FILE, "r");
@@ -4971,8 +4986,15 @@ readRecoveryCommandFile(void)
 
        FreeFile(fd);
 
+       /* set up callback to identify file parsing information on failure */
+       errcallback.callback = readRecoveryErrorCallback;
+       errcallback.previous = error_context_stack;
+       error_context_stack = &errcallback;
+
        for (item = head; item; item = item->next)
        {
+               errcallback.arg = (void *) item;
+
                if (strcmp(item->name, "restore_command") == 0)
                {
                        recoveryRestoreCommand = pstrdup(item->value);
@@ -5179,6 +5201,9 @@ readRecoveryCommandFile(void)
                                                        item->name)));
        }
 
+       /* Done, clean up */
+       error_context_stack = errcallback.previous;
+
        /*
         * Check for compulsory parameters
         */
-- 
2.9.3

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to