Hi,

Here is a patch that implements "named restore points".

It allows DBAs to specify  an exact point to which they can recover
but that point will have a name, so they have a better control of when
they want to stop recovery (ie: DBA's won't depend of remember
specific times, dates and such).

This adds a new function: pg_create_restore_point(text) (i'm not
wedded with the name so if someone wants to suggest something better,
that's fine with me), a new xlog record and a new recovery_target
parameter in recovery.conf

-- 
Jaime Casanova         www.2ndQuadrant.com
Professional PostgreSQL: Soporte y capacitación de PostgreSQL
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index db7c834..e12720c 100644
*** a/doc/src/sgml/backup.sgml
--- b/doc/src/sgml/backup.sgml
*************** restore_command = 'cp /mnt/server/archiv
*** 1075,1083 ****
      the junior DBA dropped your main transaction table), just specify the
      required stopping point in <filename>recovery.conf</>.  You can specify
      the stop point, known as the <quote>recovery target</>, either by
!     date/time or by completion of a specific transaction ID.  As of this
!     writing only the date/time option is very usable, since there are no tools
!     to help you identify with any accuracy which transaction ID to use.
     </para>
  
     <note>
--- 1075,1084 ----
      the junior DBA dropped your main transaction table), just specify the
      required stopping point in <filename>recovery.conf</>.  You can specify
      the stop point, known as the <quote>recovery target</>, either by
!     date/time, a named restore point or by completion of a specific transaction ID.  
!     As of this writing, there are no tools to help you identify with any accuracy 
!     which transaction ID to use so only date/time and named restore points are
!     useful.
     </para>
  
     <note>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04769f1..d335104 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT set_config('log_statement_stats',
*** 13930,13935 ****
--- 13930,13938 ----
      <primary>pg_switch_xlog</primary>
     </indexterm>
     <indexterm>
+     <primary>pg_create_restore_point</primary>
+    </indexterm>
+    <indexterm>
      <primary>pg_xlogfile_name</primary>
     </indexterm>
     <indexterm>
*************** SELECT set_config('log_statement_stats',
*** 13988,13993 ****
--- 13991,14003 ----
        </row>
        <row>
         <entry>
+         <literal><function>pg_create_restore_point(<parameter>name</> <type>text</> )</function></literal>
+         </entry>
+        <entry><type>text</type></entry>
+        <entry>Establish a named point for restore (restricted to superusers)</entry>
+       </row>
+       <row>
+        <entry>
          <literal><function>pg_xlogfile_name(<parameter>location</> <type>text</>)</function></literal>
          </entry>
         <entry><type>text</type></entry>
diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
index bd9dfd1..b3fc001 100644
*** a/doc/src/sgml/recovery-config.sgml
--- b/doc/src/sgml/recovery-config.sgml
*************** restore_command = 'copy "C:\\server\\arc
*** 144,149 ****
--- 144,171 ----
      <title>Recovery target settings</title>
       <variablelist>
  
+      <varlistentry id="recovery-target-name" xreflabel="recovery_target_name">
+       <term><varname>recovery_target_name</varname>
+            (<type>string</type>)
+       </term>
+       <indexterm>
+         <primary><varname>recovery_target_name</> recovery parameter</primary>
+       </indexterm>
+       <listitem>
+        <para>
+         This parameter specifies a named restore point, created with
+         <function>pg_create_restore_point()</>, to which recovery
+         will proceed.
+         At most one of <varname>recovery_target_name</>,
+         <xref linkend="recovery-target-time"> or
+         <xref linkend="recovery-target-xid"> can be specified.
+         The default is to recover to the end of the WAL log.
+         The precise stopping point is also influenced by
+         <xref linkend="recovery-target-inclusive">.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry id="recovery-target-time" xreflabel="recovery_target_time">
        <term><varname>recovery_target_time</varname>
             (<type>timestamp</type>)
*************** restore_command = 'copy "C:\\server\\arc
*** 155,161 ****
         <para>
          This parameter specifies the time stamp up to which recovery
          will proceed.
!         At most one of <varname>recovery_target_time</> and
          <xref linkend="recovery-target-xid"> can be specified.
          The default is to recover to the end of the WAL log.
          The precise stopping point is also influenced by
--- 177,184 ----
         <para>
          This parameter specifies the time stamp up to which recovery
          will proceed.
!         At most one of <varname>recovery_target_time</>,
!         <xref linkend="recovery-target-name"> or
          <xref linkend="recovery-target-xid"> can be specified.
          The default is to recover to the end of the WAL log.
          The precise stopping point is also influenced by
*************** restore_command = 'copy "C:\\server\\arc
*** 177,183 ****
          start, transactions can complete in a different numeric order.
          The transactions that will be recovered are those that committed
          before (and optionally including) the specified one.
!         At most one of <varname>recovery_target_xid</> and
          <xref linkend="recovery-target-time"> can be specified.
          The default is to recover to the end of the WAL log.
          The precise stopping point is also influenced by
--- 200,207 ----
          start, transactions can complete in a different numeric order.
          The transactions that will be recovered are those that committed
          before (and optionally including) the specified one.
!         At most one of <varname>recovery_target_xid</>,
!         <xref linkend="recovery-target-name"> or
          <xref linkend="recovery-target-time"> can be specified.
          The default is to recover to the end of the WAL log.
          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 f8f6f9b..552aae1 100644
*** a/src/backend/access/transam/recovery.conf.sample
--- b/src/backend/access/transam/recovery.conf.sample
***************
*** 66,76 ****
  # 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, or
! # by timestamp. 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).
  #
  #recovery_target_time = ''	# e.g. '2004-07-14 22:39:00 EST'
  #
  #recovery_target_xid = ''
--- 66,78 ----
  # 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, a named point
! # or by timestamp. 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).
  #
+ #recovery_target_name = ''
+ #
  #recovery_target_time = ''	# e.g. '2004-07-14 22:39:00 EST'
  #
  #recovery_target_xid = ''
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5b6a230..a84e3f4 100644
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
*************** static RecoveryTargetType recoveryTarget
*** 186,191 ****
--- 186,192 ----
  static bool recoveryTargetInclusive = true;
  static TransactionId recoveryTargetXid;
  static TimestampTz recoveryTargetTime;
+ static char *recoveryTargetName;
  
  /* options taken from recovery.conf for XLOG streaming */
  static bool StandbyMode = false;
*************** static char *TriggerFile = NULL;
*** 195,200 ****
--- 196,202 ----
  /* if recoveryStopsHere returns true, it saves actual stop xid/time here */
  static TransactionId recoveryStopXid;
  static TimestampTz recoveryStopTime;
+ static char recoveryStopNamedRestorePoint[MAXFNAMELEN];
  static bool recoveryStopAfter;
  
  /*
*************** typedef struct xl_parameter_change
*** 541,546 ****
--- 543,557 ----
  	int			wal_level;
  } xl_parameter_change;
  
+ /* 
+  * named restore points
+  */
+ typedef struct xl_named_restore_points
+ {
+ 	TimestampTz xtime;
+ 	char		name[MAXFNAMELEN];
+ } xl_named_restore_points;
+ 
  /*
   * Flags set by interrupt handlers for later service in the redo loop.
   */
*************** writeTimeLineHistory(TimeLineID newTLI,
*** 4378,4383 ****
--- 4389,4402 ----
  				 xlogfname,
  				 recoveryStopAfter ? "after" : "before",
  				 timestamptz_to_str(recoveryStopTime));
+ 	else if (recoveryTarget == RECOVERY_TARGET_NAME)
+ 		snprintf(buffer, sizeof(buffer),
+ 				 "%s%u\t%s\t%s named restore point %s\n",
+ 				 (srcfd < 0) ? "" : "\n",
+ 				 parentTLI,
+ 				 xlogfname,
+ 				 recoveryStopAfter ? "after" : "before",
+ 				 recoveryStopNamedRestorePoint);
  	else
  		snprintf(buffer, sizeof(buffer),
  				 "%s%u\t%s\tno recovery target specified\n",
*************** readRecoveryCommandFile(void)
*** 5101,5108 ****
--- 5120,5142 ----
  				ereport(DEBUG2,
  						(errmsg("recovery_target_timeline = latest")));
  		}
+ 		else if (strcmp(item->name, "recovery_target_name") == 0)
+ 		{
+ 			recoveryTargetName = pstrdup(item->value);
+ 			ereport(DEBUG2,
+ 					(errmsg("recovery_target_name = '%s'",
+ 							recoveryTargetName)));
+ 			recoveryTarget = RECOVERY_TARGET_NAME;
+ 		}
  		else if (strcmp(item->name, "recovery_target_xid") == 0)
  		{
+ 			/*
+ 			 * if recovery_target_name specified, then this overrides
+ 			 * recovery_target_xid
+ 			 */
+ 			if (recoveryTarget == RECOVERY_TARGET_NAME)
+ 				continue;
+ 
  			errno = 0;
  			recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
  			if (errno == EINVAL || errno == ERANGE)
*************** readRecoveryCommandFile(void)
*** 5117,5126 ****
  		else if (strcmp(item->name, "recovery_target_time") == 0)
  		{
  			/*
! 			 * if recovery_target_xid specified, then this overrides
! 			 * recovery_target_time
  			 */
! 			if (recoveryTarget == RECOVERY_TARGET_XID)
  				continue;
  			recoveryTarget = RECOVERY_TARGET_TIME;
  
--- 5151,5160 ----
  		else if (strcmp(item->name, "recovery_target_time") == 0)
  		{
  			/*
! 			 * if recovery_target_xid or recovery_target_name specified, 
! 			 * then this overrides recovery_target_time
  			 */
! 			if (recoveryTarget == RECOVERY_TARGET_XID || recoveryTarget == RECOVERY_TARGET_NAME)
  				continue;
  			recoveryTarget = RECOVERY_TARGET_TIME;
  
*************** exitArchiveRecovery(TimeLineID endTLI, u
*** 5358,5371 ****
  static bool
  recoveryStopsHere(XLogRecord *record, bool *includeThis)
  {
  	bool		stopsHere;
  	uint8		record_info;
  	TimestampTz recordXtime;
  
! 	/* We only consider stopping at COMMIT or ABORT records */
  	if (record->xl_rmid != RM_XACT_ID)
! 		return false;
  	record_info = record->xl_info & ~XLR_INFO_MASK;
  	if (record_info == XLOG_XACT_COMMIT)
  	{
  		xl_xact_commit *recordXactCommitData;
--- 5392,5418 ----
  static bool
  recoveryStopsHere(XLogRecord *record, bool *includeThis)
  {
+ 	bool		couldStop;
  	bool		stopsHere;
  	uint8		record_info;
  	TimestampTz recordXtime;
  
! 	/* 
! 	 * We could stop at COMMIT or ABORT records 
! 	 */
! 	couldStop = true;
  	if (record->xl_rmid != RM_XACT_ID)
! 		couldStop = false;
! 	/*
! 	 * Or when we found a named restore point
! 	 */
  	record_info = record->xl_info & ~XLR_INFO_MASK;
+ 	if ((record->xl_rmid == RM_XLOG_ID) && (record_info == XLOG_RESTORE_POINT))
+ 		couldStop = true;
+ 
+ 	if (!couldStop)
+ 		return false;
+ 
  	if (record_info == XLOG_XACT_COMMIT)
  	{
  		xl_xact_commit *recordXactCommitData;
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5380,5385 ****
--- 5427,5439 ----
  		recordXactAbortData = (xl_xact_abort *) XLogRecGetData(record);
  		recordXtime = recordXactAbortData->xact_time;
  	}
+ 	else if (record_info == XLOG_RESTORE_POINT)
+ 	{
+ 		xl_named_restore_points *recordNamedRestorePoint;
+ 
+ 		recordNamedRestorePoint = (xl_named_restore_points *) XLogRecGetData(record);
+ 		recordXtime = recordNamedRestorePoint->xtime;
+ 	}
  	else
  		return false;
  
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5405,5411 ****
  		if (stopsHere)
  			*includeThis = recoveryTargetInclusive;
  	}
! 	else
  	{
  		/*
  		 * there can be many transactions that share the same commit time, so
--- 5459,5465 ----
  		if (stopsHere)
  			*includeThis = recoveryTargetInclusive;
  	}
! 	else if (recoveryTarget == RECOVERY_TARGET_TIME)
  	{
  		/*
  		 * there can be many transactions that share the same commit time, so
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5419,5424 ****
--- 5473,5492 ----
  		if (stopsHere)
  			*includeThis = false;
  	}
+ 	else
+ 	{
+ 		char name[MAXFNAMELEN];
+ 
+ 		strncpy(name, ((xl_named_restore_points *) XLogRecGetData(record))->name, MAXFNAMELEN);
+ 		stopsHere = (strncmp(name, recoveryTargetName, MAXFNAMELEN) == 0);
+ 
+ 		/* 
+ 		 * Because this record does nothing but hold the restore point name
+ 		 * applying or not this record is just waste so recoveryTargetInclusive 
+ 		 * is meaningless in this case
+ 		 */
+ 		*includeThis = false;	
+ 	}
  
  	if (stopsHere)
  	{
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5426,5431 ****
--- 5494,5502 ----
  		recoveryStopTime = recordXtime;
  		recoveryStopAfter = *includeThis;
  
+ 		if (recoveryTarget == RECOVERY_TARGET_NAME)
+ 			strncpy(recoveryStopNamedRestorePoint, recoveryTargetName, MAXFNAMELEN);
+ 
  		if (record_info == XLOG_XACT_COMMIT)
  		{
  			if (recoveryStopAfter)
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5439,5445 ****
  								recoveryStopXid,
  								timestamptz_to_str(recoveryStopTime))));
  		}
! 		else
  		{
  			if (recoveryStopAfter)
  				ereport(LOG,
--- 5510,5516 ----
  								recoveryStopXid,
  								timestamptz_to_str(recoveryStopTime))));
  		}
! 		else if (record_info == XLOG_XACT_ABORT)
  		{
  			if (recoveryStopAfter)
  				ereport(LOG,
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5452,5457 ****
--- 5523,5533 ----
  								recoveryStopXid,
  								timestamptz_to_str(recoveryStopTime))));
  		}
+ 		else
+ 			ereport(LOG,
+ 						(errmsg("recovery stopping at named point %s, time %s",
+ 								recoveryStopNamedRestorePoint,
+ 								timestamptz_to_str(recoveryStopTime))));
  
  		if (recoveryStopAfter)
  			SetLatestXTime(recordXtime);
*************** XLogPutNextOid(Oid nextOid)
*** 7774,7779 ****
--- 7850,7877 ----
  }
  
  /*
+  * Write a restore point record
+  */
+ XLogRecPtr
+ XLogRestorePointName(char *name)
+ {
+ 	XLogRecPtr	RecPtr;
+ 	XLogRecData rdata;
+ 	xl_named_restore_points xlrec;
+ 
+ 	xlrec.xtime = GetCurrentTimestamp();
+ 	strncpy(xlrec.name, name, MAXFNAMELEN);
+ 
+ 	rdata.data = (char *) &xlrec;
+ 	rdata.len = sizeof(xl_named_restore_points);
+ 	rdata.buffer = InvalidBuffer;
+ 	rdata.next = NULL;
+ 	RecPtr = XLogInsert(RM_XLOG_ID, XLOG_RESTORE_POINT, &rdata);
+ 
+ 	return RecPtr;
+ }
+ 
+ /*
   * Write an XLOG SWITCH record.
   *
   * Here we just blindly issue an XLogInsert request for the record.
*************** xlog_redo(XLogRecPtr lsn, XLogRecord *re
*** 7988,7993 ****
--- 8086,8095 ----
  	{
  		/* nothing to do here */
  	}
+ 	else if (info == XLOG_RESTORE_POINT)
+ 	{
+ 		/* nothing to do here */
+ 	}
  	else if (info == XLOG_SWITCH)
  	{
  		/* nothing to do here */
*************** xlog_desc(StringInfo buf, uint8 xl_info,
*** 8127,8132 ****
--- 8229,8244 ----
  						 xlrec.max_locks_per_xact,
  						 wal_level_str);
  	}
+ 	else if (info == XLOG_RESTORE_POINT)
+ 	{
+ 		xl_named_restore_points xlrec;
+ 		char name[MAXFNAMELEN];
+ 
+ 		memcpy(&xlrec, rec, sizeof(xl_named_restore_points));
+ 		strncpy(name, xlrec.name, MAXFNAMELEN);
+ 
+ 		appendStringInfo(buf, "named restore point: '%s'", name);
+ 	}
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
*************** do_pg_abort_backup(void)
*** 8755,8760 ****
--- 8867,8909 ----
  }
  
  /*
+  * pg_create_restore_point: a named point for restore
+  */
+ Datum
+ pg_create_restore_point(PG_FUNCTION_ARGS)
+ {
+ 	text	   *restore_name = PG_GETARG_TEXT_P(0);
+ 	char	   *restore_name_str;
+ 	XLogRecPtr	restorepoint;
+ 	char		location[MAXFNAMELEN];
+ 
+     if (!superuser())
+         ereport(ERROR,
+                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+              (errmsg("must be superuser to create a restore point"))));
+ 
+     if (RecoveryInProgress())
+         ereport(ERROR,
+                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                  errmsg("recovery is in progress"),
+                  errhint("WAL control functions cannot be executed during recovery.")));
+ 
+ 	if (!XLogArchivingActive())
+         ereport(NOTICE,
+                  (errmsg("WAL archiving is not enabled; you must ensure that WAL segments are copied through other means for restore points to be usefull for you")));
+ 
+ 	restore_name_str = text_to_cstring(restore_name);
+ 	restorepoint = XLogRestorePointName(restore_name_str);
+ 
+ 	/*
+ 	 * As a convenience, return the WAL location of the restore point record
+ 	 */
+ 	snprintf(location, sizeof(location), "%X/%X",
+ 			 restorepoint.xlogid, restorepoint.xrecoff);
+ 	PG_RETURN_TEXT_P(cstring_to_text(location));
+ }
+ 
+ /*
   * pg_switch_xlog: switch to next xlog file
   */
  Datum
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 74d3427..3394152 100644
*** a/src/include/access/xlog.h
--- b/src/include/access/xlog.h
*************** typedef enum
*** 184,190 ****
  {
  	RECOVERY_TARGET_UNSET,
  	RECOVERY_TARGET_XID,
! 	RECOVERY_TARGET_TIME
  } RecoveryTargetType;
  
  extern XLogRecPtr XactLastRecEnd;
--- 184,191 ----
  {
  	RECOVERY_TARGET_UNSET,
  	RECOVERY_TARGET_XID,
! 	RECOVERY_TARGET_TIME,
! 	RECOVERY_TARGET_NAME
  } RecoveryTargetType;
  
  extern XLogRecPtr XactLastRecEnd;
*************** extern void InitXLOGAccess(void);
*** 302,307 ****
--- 303,309 ----
  extern void CreateCheckPoint(int flags);
  extern bool CreateRestartPoint(int flags);
  extern void XLogPutNextOid(Oid nextOid);
+ extern XLogRecPtr XLogRestorePointName(char *name);
  extern XLogRecPtr GetRedoRecPtr(void);
  extern XLogRecPtr GetInsertRecPtr(void);
  extern XLogRecPtr GetFlushRecPtr(void);
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index ba64035..dd5a252 100644
*** a/src/include/access/xlog_internal.h
--- b/src/include/access/xlog_internal.h
*************** extern XLogRecPtr RequestXLogSwitch(void
*** 266,271 ****
--- 266,272 ----
   */
  extern Datum pg_start_backup(PG_FUNCTION_ARGS);
  extern Datum pg_stop_backup(PG_FUNCTION_ARGS);
+ extern Datum pg_create_restore_point(PG_FUNCTION_ARGS);
  extern Datum pg_switch_xlog(PG_FUNCTION_ARGS);
  extern Datum pg_current_xlog_location(PG_FUNCTION_ARGS);
  extern Datum pg_current_xlog_insert_location(PG_FUNCTION_ARGS);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index ddf1398..43c1abe 100644
*** a/src/include/catalog/pg_control.h
--- b/src/include/catalog/pg_control.h
*************** typedef struct CheckPoint
*** 59,64 ****
--- 59,65 ----
  #define XLOG_SWITCH						0x40
  #define XLOG_BACKUP_END					0x50
  #define XLOG_PARAMETER_CHANGE			0x60
+ #define XLOG_RESTORE_POINT				0x70
  
  
  /*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f8b5d4d..b037109 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2172 ( pg_start_backup
*** 3395,3400 ****
--- 3395,3402 ----
  DESCR("prepare for taking an online backup");
  DATA(insert OID = 2173 ( pg_stop_backup			PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
  DESCR("finish taking an online backup");
+ DATA(insert OID = 2847 ( pg_create_restore_point		PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_create_restore_point _null_ _null_ _null_ ));
+ DESCR("create a named restore point");
  DATA(insert OID = 2848 ( pg_switch_xlog			PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_switch_xlog _null_ _null_ _null_ ));
  DESCR("switch to new xlog file");
  DATA(insert OID = 2849 ( pg_current_xlog_location	PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_current_xlog_location _null_ _null_ _null_ ));
-- 
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