diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 4521496352..b93c268167 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1315,7 +1315,7 @@ The commands accepted in walsender mode are: Requests the server to identify itself. Server replies with a result - set of a single row, containing two fields: + set of a single row, containing three fields: @@ -1344,6 +1344,19 @@ The commands accepted in walsender mode are: + + + + xlogpos + + + + Current xlog write location. Useful to get a known location in the + transaction log where streaming can start. + + + + @@ -1520,15 +1533,16 @@ The commands accepted in walsender mode are: - When the backup is started, the server will first send a header in - ordinary result set format, followed by one or more CopyResponse - results, one for PGDATA and one for each additional tablespace other - than pg_default and pg_global. The data in - the CopyResponse results will be a tar format (using ustar00 - extensions) dump of the tablespace contents. + When the backup is started, the server will first send two + ordinary result sets, followed by one or more CopyResponse + results. - The header is an ordinary resultset with one row for each tablespace. + The first ordinary result set contains the starting position of the + backup, given in XLogRecPtr format as a single column in a single row. + + + The second ordinary result set has one row for each tablespace. The fields in this row are: @@ -1560,6 +1574,15 @@ The commands accepted in walsender mode are: + + After the second regular result set, one or more CopyResponse results + will be sent, one for PGDATA and one for each additional tablespace other + than pg_default and pg_global. The data in + the CopyResponse results will be a tar format (using ustar00 + extensions) dump of the tablespace contents. After the tar data is + complete, a final ordinary result set will be sent. + + The tar archive for the data directory and each tablespace will contain all files in the directories, regardless of whether they are @@ -1583,6 +1606,11 @@ The commands accepted in walsender mode are: Owner, group and file mode are set if the underlying filesystem on the server supports it. + + Once all tablespaces have been sent, a final regular result set will + be sent. This result set contains the end position of the + backup, given in XLogRecPtr format as a single column in a single row. + diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index 29284a6ab5..b5cda5063b 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -52,6 +52,7 @@ static void SendBackupHeader(List *tablespaces); static void base_backup_cleanup(int code, Datum arg); static void perform_base_backup(basebackup_options *opt, DIR *tblspcdir); static void parse_basebackup_options(List *options, basebackup_options *opt); +static void SendXlogRecPtrResult(XLogRecPtr ptr); /* * Size of each block sent into the tar stream for larger files. @@ -92,6 +93,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir) char *labelfile; startptr = do_pg_start_backup(opt->label, opt->fastcheckpoint, &labelfile); + SendXlogRecPtrResult(startptr); PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0); { @@ -239,6 +241,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir) /* Send CopyDone message for the last tar file */ pq_putemptymessage('c'); } + SendXlogRecPtrResult(endptr); } /* @@ -431,6 +434,42 @@ SendBackupHeader(List *tablespaces) pq_puttextmessage('C', "SELECT"); } +/* + * Send a single resultset containing just a single + * XlogRecPtr record (in text format) + */ +static void +SendXlogRecPtrResult(XLogRecPtr ptr) +{ + StringInfoData buf; + char str[MAXFNAMELEN]; + + snprintf(str, sizeof(str), "%X/%X", ptr.xlogid, ptr.xrecoff); + + pq_beginmessage(&buf, 'T'); /* RowDescription */ + pq_sendint(&buf, 1, 2); /* 1 field */ + + /* Field header */ + pq_sendstring(&buf, "recptr"); + pq_sendint(&buf, 0, 4); /* table oid */ + pq_sendint(&buf, 0, 2); /* attnum */ + pq_sendint(&buf, TEXTOID, 4); /* type oid */ + pq_sendint(&buf, -1, 2); + pq_sendint(&buf, 0, 4); + pq_sendint(&buf, 0, 2); + pq_endmessage(&buf); + + /* Data row */ + pq_beginmessage(&buf, 'D'); + pq_sendint(&buf, 1, 2); /* number of columns */ + pq_sendint(&buf, strlen(str), 4); /* length */ + pq_sendbytes(&buf, str, strlen(str)); + pq_endmessage(&buf); + + /* Send a CommandComplete message */ + pq_puttextmessage('C', "SELECT"); +} + /* * Inject a file with given name and content in the output tar stream. */ diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index f70458e01a..78963c1e6b 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -258,19 +258,26 @@ IdentifySystem(void) StringInfoData buf; char sysid[32]; char tli[11]; + char xpos[MAXFNAMELEN]; + XLogRecPtr logptr; /* - * Reply with a result set with one row, two columns. First col is system - * ID, and second is timeline ID + * Reply with a result set with one row, three columns. First col is system + * ID, second is timeline ID, and third is current xlog location. */ snprintf(sysid, sizeof(sysid), UINT64_FORMAT, GetSystemIdentifier()); snprintf(tli, sizeof(tli), "%u", ThisTimeLineID); + logptr = GetInsertRecPtr(); + + snprintf(xpos, sizeof(xpos), "%X/%X", + logptr.xlogid, logptr.xrecoff); + /* Send a RowDescription message */ pq_beginmessage(&buf, 'T'); - pq_sendint(&buf, 2, 2); /* 2 fields */ + pq_sendint(&buf, 3, 2); /* 3 fields */ /* first field */ pq_sendstring(&buf, "systemid"); /* col name */ @@ -289,15 +296,27 @@ IdentifySystem(void) pq_sendint(&buf, 4, 2); /* typlen */ pq_sendint(&buf, 0, 4); /* typmod */ pq_sendint(&buf, 0, 2); /* format code */ + + /* third field */ + pq_sendstring(&buf, "xlogpos"); + pq_sendint(&buf, 0, 4); + pq_sendint(&buf, 0, 2); + pq_sendint(&buf, TEXTOID, 4); + pq_sendint(&buf, -1, 2); + pq_sendint(&buf, 0, 4); + pq_sendint(&buf, 0, 2); pq_endmessage(&buf); /* Send a DataRow message */ pq_beginmessage(&buf, 'D'); - pq_sendint(&buf, 2, 2); /* # of columns */ + pq_sendint(&buf, 3, 2); /* # of columns */ pq_sendint(&buf, strlen(sysid), 4); /* col1 len */ pq_sendbytes(&buf, (char *) &sysid, strlen(sysid)); pq_sendint(&buf, strlen(tli), 4); /* col2 len */ pq_sendbytes(&buf, (char *) tli, strlen(tli)); + pq_sendint(&buf, strlen(xpos), 4); /* col3 len */ + pq_sendbytes(&buf, (char *) xpos, strlen(xpos)); + pq_endmessage(&buf); /* Send CommandComplete and ReadyForQuery messages */ diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index 6708fb7bf5..98414a99c6 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -742,15 +742,40 @@ static void BaseBackup() { PGresult *res; + uint32 timeline; char current_path[MAXPGPATH]; char escaped_label[MAXPGPATH]; int i; + char xlogstart[64]; + char xlogend[64]; /* * Connect in replication mode to the server */ conn = GetConnection(); + /* + * Run IDENFITY_SYSTEM so we can get the timeline + */ + res = PQexec(conn, "IDENTIFY_SYSTEM"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, _("%s: could not identify system: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + if (PQntuples(res) != 1) + { + fprintf(stderr, _("%s: could not identify system, got %i rows\n"), + progname, PQntuples(res)); + disconnect_and_exit(1); + } + timeline = atoi(PQgetvalue(res, 0, 1)); + PQclear(res); + + /* + * Start the actual backup + */ PQescapeStringConn(conn, escaped_label, label, sizeof(escaped_label), &i); snprintf(current_path, sizeof(current_path), "BASE_BACKUP LABEL '%s' %s %s %s", escaped_label, @@ -766,7 +791,7 @@ BaseBackup() } /* - * Get the header + * Get the starting xlog position */ res = PQgetResult(conn); if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -775,6 +800,28 @@ BaseBackup() progname, PQerrorMessage(conn)); disconnect_and_exit(1); } + if (PQntuples(res) != 1) + { + fprintf(stderr, _("%s: no start point returned from server.\n"), + progname); + disconnect_and_exit(1); + } + strcpy(xlogstart, PQgetvalue(res, 0, 0)); + if (verbose && includewal) + fprintf(stderr, "xlog start point: %s\n", xlogstart); + PQclear(res); + MemSet(xlogend, 0, sizeof(xlogend)); + + /* + * Get the header + */ + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, _("%s: could not get backup header: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } if (PQntuples(res) < 1) { fprintf(stderr, _("%s: no data returned from server.\n"), progname); @@ -828,6 +875,27 @@ BaseBackup() } PQclear(res); + /* + * Get the stop position + */ + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, _("%s: could not get end xlog position from server.\n"), + progname); + disconnect_and_exit(1); + } + if (PQntuples(res) != 1) + { + fprintf(stderr, _("%s: no end point returned from server.\n"), + progname); + disconnect_and_exit(1); + } + strcpy(xlogend, PQgetvalue(res, 0, 0)); + if (verbose && includewal) + fprintf(stderr, "xlog end point: %s\n", xlogend); + PQclear(res); + res = PQgetResult(conn); if (PQresultStatus(res) != PGRES_COMMAND_OK) {