From 5358def958af3ca0ed45a008dafafe5aaff290a0 Mon Sep 17 00:00:00 2001
From: Yura Sokolov <y.sokolov@postgrespro.ru>
Date: Tue, 18 Jun 2024 14:02:14 +0300
Subject: [PATCH v2 2/2] Add timestamp to XLOG_BACKUP_END.

It will allow to have exact recovery time for backup, so user may
use it as recovery_target_time.
---
 src/backend/access/rmgrdesc/xlogdesc.c      |  8 ++++---
 src/backend/access/transam/xlog.c           |  6 ++++--
 src/backend/access/transam/xlogrecovery.c   | 11 ++++++++++
 src/include/access/xlog_internal.h          |  7 +++++++
 src/test/recovery/t/003_recovery_targets.pl | 23 ++++++++++++++++++---
 src/tools/pgindent/typedefs.list            |  1 +
 6 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index e455400716d..17702e73341 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -86,10 +86,12 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 	}
 	else if (info == XLOG_BACKUP_END)
 	{
-		XLogRecPtr	startpoint;
+		xl_backup_end xlrec;
 
-		memcpy(&startpoint, rec, sizeof(XLogRecPtr));
-		appendStringInfo(buf, "%X/%X", LSN_FORMAT_ARGS(startpoint));
+		memcpy(&xlrec, rec, sizeof(xl_backup_end));
+		appendStringInfo(buf, "%X/%X; time %s",
+						 LSN_FORMAT_ARGS(xlrec.startpoint),
+						 timestamptz_to_str(xlrec.end_time));
 	}
 	else if (info == XLOG_PARAMETER_CHANGE)
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 330e058c5f2..c82bb1a208b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -9162,14 +9162,16 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 	}
 	else
 	{
+		xl_backup_end xlrec;
 		char	   *history_file;
 
 		/*
 		 * Write the backup-end xlog record
 		 */
+		xlrec.startpoint = state->startpoint;
+		xlrec.end_time = GetCurrentTimestamp();
 		XLogBeginInsert();
-		XLogRegisterData((char *) (&state->startpoint),
-						 sizeof(state->startpoint));
+		XLogRegisterData((char *) (&xlrec), sizeof(xlrec));
 		state->stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
 
 		/*
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index e1ba4452113..96a41c7a449 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -2434,6 +2434,11 @@ getRecordTimestamp(XLogReaderState *record, TimestampTz *recordXtime)
 		*recordXtime = ((xl_restore_point *) XLogRecGetData(record))->rp_time;
 		return true;
 	}
+	if (rmid == RM_XLOG_ID && info == XLOG_BACKUP_END)
+	{
+		*recordXtime = ((xl_backup_end *) XLogRecGetData(record))->end_time;
+		return true;
+	}
 	if (rmid == RM_XACT_ID && (xact_info == XLOG_XACT_COMMIT ||
 							   xact_info == XLOG_XACT_COMMIT_PREPARED))
 	{
@@ -2816,6 +2821,12 @@ recoveryStopsAfter(XLogReaderState *record)
 								MAXFNAMELEN, recordRestorePointData->rp_name,
 								timestamptz_to_str(recoveryStopTime))));
 			}
+			else if (info == XLOG_BACKUP_END)
+			{
+				ereport(LOG,
+						(errmsg("recovery stopping at backup end, time %s",
+								timestamptz_to_str(recoveryStopTime))));
+			}
 			else
 			{
 				ereport(LOG,
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 5161b72f281..18b03115273 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -289,6 +289,13 @@ typedef struct xl_restore_point
 	char		rp_name[MAXFNAMELEN];
 } xl_restore_point;
 
+/* backup end record */
+typedef struct xl_backup_end
+{
+	XLogRecPtr	startpoint;
+	TimestampTz end_time;
+} xl_backup_end;
+
 /* Overwrite of prior contrecord */
 typedef struct xl_overwrite_contrecord
 {
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index d5345c1bf1e..3e2b161b503 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -114,9 +114,23 @@ $node_primary->safe_psql('postgres',
 my $lsn5 = my $recovery_lsn =
   $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
 
+# Try recovery target time for backup
 $node_primary->safe_psql('postgres',
 	"INSERT INTO tab_int VALUES (generate_series(5001,6000))");
 
+my $main_h = $node_primary->background_psql('postgres');
+$main_h->query_safe("SELECT pg_backup_start('test', true)");
+print("AFTER backup_start\n");
+my $lsn6 =
+	$node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+my $out = $main_h->query_safe("SELECT now(), pg_backup_stop(false);");
+print("AFTER backup_stop\n");
+my ($recovery_backup_time) = $out =~ /(20\d\d-\d\d-\d\d [\d:.+-]+)/;
+$main_h->quit();
+
+$node_primary->safe_psql('postgres',
+	"INSERT INTO tab_int VALUES (generate_series(6001,7000))");
+
 # Force archiving of WAL file
 $node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
 
@@ -139,6 +153,9 @@ test_recovery_standby('rp_time', 'standby_4_time', $node_primary, \@recovery_par
 @recovery_params = ("recovery_target_lsn = '$recovery_lsn'");
 test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
 	"5000", $lsn5);
+@recovery_params = ("recovery_target_time = '$recovery_backup_time'");
+test_recovery_standby('be_time', 'standby_6', $node_primary, \@recovery_params,
+	"6000", $lsn6, "recovery stopping at backup end");
 
 # Multiple targets
 #
@@ -151,9 +168,9 @@ test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
 	"recovery_target_name = ''",
 	"recovery_target_time = '$recovery_time'");
 test_recovery_standby('multiple overriding settings',
-	'standby_6', $node_primary, \@recovery_params, "3000", $lsn3);
+	'standby_7', $node_primary, \@recovery_params, "3000", $lsn3);
 
-my $node_standby = PostgreSQL::Test::Cluster->new('standby_7');
+my $node_standby = PostgreSQL::Test::Cluster->new('standby_8');
 $node_standby->init_from_backup($node_primary, 'my_backup',
 	has_restoring => 1);
 $node_standby->append_conf(
@@ -173,7 +190,7 @@ ok($logfile =~ qr/multiple recovery targets specified/,
 
 # Check behavior when recovery ends before target is reached
 
-$node_standby = PostgreSQL::Test::Cluster->new('standby_8');
+$node_standby = PostgreSQL::Test::Cluster->new('standby_9');
 $node_standby->init_from_backup(
 	$node_primary, 'my_backup',
 	has_restoring => 1,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 61ad417cde6..fd043656702 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -4032,6 +4032,7 @@ worktable
 wrap
 ws_file_info
 ws_options
+xl_backup_end
 xl_brin_createidx
 xl_brin_desummarize
 xl_brin_insert
-- 
2.43.0

