1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-12 10:04:14 +02:00

Add backup functions to Db object.

These functions implement the database backup functionality for all supported versions.
This commit is contained in:
David Steele 2019-12-07 18:44:06 -05:00
parent 8766326da8
commit d2587250da
6 changed files with 1071 additions and 10 deletions

View File

@ -474,7 +474,7 @@ config/parse.o: config/parse.c build.auto.h common/assert.h common/debug.h commo
config/protocol.o: config/protocol.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/io.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h config/protocol.h protocol/client.h protocol/command.h protocol/server.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c config/protocol.c -o config/protocol.o
db/db.o: db/db.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/macro.h common/memContext.h common/object.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h db/db.h db/protocol.h postgres/client.h postgres/interface.h postgres/version.h protocol/client.h protocol/command.h protocol/server.h storage/info.h storage/read.h storage/storage.h storage/write.h version.h
db/db.o: db/db.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/macro.h common/memContext.h common/object.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h common/wait.h db/db.h db/protocol.h postgres/client.h postgres/interface.h postgres/version.h protocol/client.h protocol/command.h protocol/helper.h protocol/server.h storage/info.h storage/read.h storage/storage.h storage/write.h version.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c db/db.c -o db/db.o
db/helper.o: db/helper.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h db/db.h db/helper.h postgres/client.h postgres/interface.h protocol/client.h protocol/command.h protocol/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h version.h

View File

@ -7,12 +7,19 @@ Database Client
#include "common/log.h"
#include "common/memContext.h"
#include "common/object.h"
#include "common/wait.h"
#include "db/db.h"
#include "db/protocol.h"
#include "postgres/interface.h"
#include "postgres/version.h"
#include "protocol/helper.h"
#include "version.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
#define PG_BACKUP_ADVISORY_LOCK "12340078987004321"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
@ -231,6 +238,195 @@ dbOpen(Db *this)
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
// Helper to build start backup query
static String *
dbBackupStartQuery(unsigned int pgVersion, bool startFast)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_PARAM(BOOL, startFast);
FUNCTION_TEST_END();
// Build query to return start lsn and WAL segment name
String *result = strNewFmt(
"select lsn::text as lsn,\n"
" pg_catalog.pg_%sfile_name(lsn)::text as wal_segment_name\n"
" from pg_catalog.pg_start_backup('" PROJECT_NAME " backup started at ' || current_timestamp",
strPtr(pgWalName(pgVersion)));
// Start backup after immediate checkpoint
if (startFast)
{
strCatFmt(result, ", true");
}
// Else start backup at the next scheduled checkpoint
else if (pgVersion >= PG_VERSION_84)
strCatFmt(result, ", false");
// Use non-exclusive backup mode when available
if (pgVersion >= PG_VERSION_96)
strCatFmt(result, ", false");
// Complete query
strCatFmt(result, ") as lsn");
FUNCTION_TEST_RETURN(result);
}
#define FUNCTION_LOG_DB_BACKUP_START_RESULT_TYPE \
DbBackupStartResult
#define FUNCTION_LOG_DB_BACKUP_START_RESULT_FORMAT(value, buffer, bufferSize) \
objToLog(&value, "DbBackupStartResult", buffer, bufferSize)
DbBackupStartResult
dbBackupStart(Db *this, bool startFast, bool stopAuto)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(BOOL, startFast);
FUNCTION_LOG_PARAM(BOOL, stopAuto);
FUNCTION_LOG_END();
ASSERT(this != NULL);
DbBackupStartResult result = {.lsn = NULL};
MEM_CONTEXT_TEMP_BEGIN()
{
// Acquire the backup advisory lock to make sure that backups are not running from multiple backup servers against the same
// database cluster. This lock helps make the stop-auto option safe.
if (!varBool(dbQueryColumn(this, STRDEF("select pg_catalog.pg_try_advisory_lock(" PG_BACKUP_ADVISORY_LOCK ")::bool"))))
{
THROW(
LockAcquireError,
"unable to acquire " PROJECT_NAME " advisory lock\n"
"HINT: is another " PROJECT_NAME " backup already running on this cluster?");
}
// If stop-auto is enabled check for a running backup
if (stopAuto)
{
// This feature is not supported for PostgreSQL >= 9.6 since backups are run in non-exclusive mode
CHECK(dbPgVersion(this) >= PG_VERSION_93 && dbPgVersion(this) < PG_VERSION_96);
if (varBool(dbQueryColumn(this, STRDEF("select pg_catalog.pg_is_in_backup()::bool"))))
{
LOG_WARN(
"the cluster is already in backup mode but no " PROJECT_NAME " backup process is running."
" pg_stop_backup() will be called so a new backup can be started.");
dbBackupStop(this);
}
}
// Start backup
VariantList *row = dbQueryRow(this, dbBackupStartQuery(dbPgVersion(this), startFast));
// Return results
memContextSwitch(MEM_CONTEXT_OLD());
result.lsn = strDup(varStr(varLstGet(row, 0)));
result.walSegmentName = strDup(varStr(varLstGet(row, 1)));
memContextSwitch(MEM_CONTEXT_TEMP());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(DB_BACKUP_START_RESULT, result);
}
/**********************************************************************************************************************************/
// Helper to build stop backup query
static String *
dbBackupStopQuery(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
// Build query to return start lsn and WAL segment name
String *result = strNewFmt(
"select lsn::text as lsn,\n"
" pg_catalog.pg_%sfile_name(lsn)::text as wal_segment_name",
strPtr(pgWalName(pgVersion)));
// For PostgreSQL >= 9.6 the backup label and tablespace map are returned from pg_stop_backup
if (pgVersion >= PG_VERSION_96)
{
strCat(
result,
",\n"
" labelfile::text as backuplabel_file,\n"
" spcmapfile::text as tablespacemap_file");
}
// Build stop backup function
strCat(
result,
"\n"
" from pg_catalog.pg_stop_backup(");
// Use non-exclusive backup mode when available
if (pgVersion >= PG_VERSION_96)
strCatFmt(result, "false");
// Disable archive checking in pg_stop_backup() since we do this elsewhere
if (pgVersion >= PG_VERSION_10)
strCatFmt(result, ", false");
// Complete query
strCatFmt(result, ")");
if (pgVersion < PG_VERSION_96)
strCatFmt(result, " as lsn");
FUNCTION_TEST_RETURN(result);
}
#define FUNCTION_LOG_DB_BACKUP_STOP_RESULT_TYPE \
DbBackupStopResult
#define FUNCTION_LOG_DB_BACKUP_STOP_RESULT_FORMAT(value, buffer, bufferSize) \
objToLog(&value, "DbBackupStopResult", buffer, bufferSize)
DbBackupStopResult
dbBackupStop(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
DbBackupStopResult result = {.lsn = NULL};
MEM_CONTEXT_TEMP_BEGIN()
{
// Stop backup
VariantList *row = dbQueryRow(this, dbBackupStopQuery(dbPgVersion(this)));
// Check if the tablespace map is empty
bool tablespaceMapEmpty =
dbPgVersion(this) >= PG_VERSION_96 ? strSize(strTrim(strDup(varStr(varLstGet(row, 3))))) == 0 : false;
// Return results
memContextSwitch(MEM_CONTEXT_OLD());
result.lsn = strDup(varStr(varLstGet(row, 0)));
result.walSegmentName = strDup(varStr(varLstGet(row, 1)));
if (dbPgVersion(this) >= PG_VERSION_96)
{
result.backupLabel = strDup(varStr(varLstGet(row, 2)));
if (!tablespaceMapEmpty)
result.tablespaceMap = strDup(varStr(varLstGet(row, 3)));
}
memContextSwitch(MEM_CONTEXT_TEMP());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(DB_BACKUP_STOP_RESULT, result);
}
/***********************************************************************************************************************************
Is this instance a standby?
***********************************************************************************************************************************/
@ -253,6 +449,155 @@ dbIsStandby(Db *this)
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
VariantList *
dbList(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
FUNCTION_LOG_RETURN(
VARIANT_LIST, dbQuery(this, STRDEF("select oid::oid, datname::text, datlastsysoid::oid from pg_catalog.pg_database")));
}
/**********************************************************************************************************************************/
void
dbReplayWait(Db *this, const String *targetLsn, TimeMSec timeout)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(STRING, targetLsn);
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(targetLsn != NULL);
ASSERT(timeout > 0);
MEM_CONTEXT_TEMP_BEGIN()
{
// Loop until lsn has been reached or timeout
Wait *wait = waitNew(timeout);
bool targetReached = false;
const char *lsnName = strPtr(pgLsnName(dbPgVersion(this)));
const String *replayLsnFunction = strNewFmt(
"pg_catalog.pg_last_%s_replay_%s()", strPtr(pgWalName(dbPgVersion(this))), lsnName);
const String *replayLsn = NULL;
do
{
// Build the query
String *query = strNewFmt(
"select replayLsn::text,\n"
" (replayLsn > '%s')::bool as targetReached",
strPtr(targetLsn));
if (replayLsn != NULL)
{
strCatFmt(
query,
",\n"
" (replayLsn > '%s')::bool as replayProgress", strPtr(replayLsn));
}
strCatFmt(
query,
"\n"
" from %s as replayLsn",
strPtr(replayLsnFunction));
// Execute the query and get replayLsn
VariantList *row = dbQueryRow(this, query);
replayLsn = varStr(varLstGet(row, 0));
// Error when replayLsn is null which indicates that this is not a standby. This should have been sorted out before we
// connected but it's possible that the standy was promoted in the meantime.
if (replayLsn == NULL)
{
THROW_FMT(
ArchiveTimeoutError,
"unable to query replay lsn on the standby using '%s'\n"
"HINT: Is this a standby?",
strPtr(replayLsnFunction));
}
targetReached = varBool(varLstGet(row, 1));
// If the target has not been reached but progress is being made then reset the timer
if (!targetReached && varLstSize(row) > 2 && varBool(varLstGet(row, 2)))
wait = waitNew(timeout);
protocolKeepAlive();
}
while (!targetReached && waitMore(wait));
// Error if a timeout occurred before the target lsn was reached
if (!targetReached)
{
THROW_FMT(
ArchiveTimeoutError, "timeout before standby replayed to %s - only reached %s", strPtr(targetLsn),
strPtr(replayLsn));
}
// Perform a checkpoint
dbExec(this, STRDEF("checkpoint"));
// On PostgreSQL >= 9.6 the checkpoint location can be verified
//
// ??? We have seen one instance where this check failed. Is there any chance that the replayed position could be ahead of
// the checkpoint recorded in pg_control? It seems possible since it would be safer if the checkpoint in pg_control was
// behind rather than ahead, so add a loop to keep checking until the checkpoint has been recorded or timeout.
if (dbPgVersion(this) >= PG_VERSION_96)
{
// Build the query
const String *query = strNewFmt(
"select (checkpoint_%s > '%s')::bool as targetReached,\n"
" checkpoint_%s::text as checkpointLsn\n"
" from pg_catalog.pg_control_checkpoint()",
lsnName, strPtr(targetLsn), lsnName);
// Execute query
VariantList *row = dbQueryRow(this, query);
// Verify target was reached
if (!varBool(varLstGet(row, 0)))
{
THROW_FMT(
ArchiveTimeoutError,
"the checkpoint lsn %s is less than the target lsn %s even though the replay lsn is %s",
strPtr(varStr(varLstGet(row, 1))), strPtr(targetLsn), strPtr(replayLsn));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
VariantList *
dbTablespaceList(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
FUNCTION_LOG_RETURN(VARIANT_LIST, dbQuery(this, STRDEF("select oid::oid, spcname::text from pg_catalog.pg_tablespace")));
}
/**********************************************************************************************************************************/
TimeMSec
dbTimeMSec(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
FUNCTION_LOG_RETURN(
TIME_MSEC, varUInt64Force(dbQueryColumn(this, STRDEF("select (extract(epoch from clock_timestamp()) * 1000)::bigint"))));
}
/***********************************************************************************************************************************
Switch the WAL segment and return the segment that should have been archived
***********************************************************************************************************************************/

View File

@ -27,7 +27,41 @@ Db *dbNew(PgClient *client, ProtocolClient *remoteClient, const String *applicat
Functions
***********************************************************************************************************************************/
void dbOpen(Db *this);
// Start backup and return starting lsn and wal segment name
typedef struct DbBackupStartResult
{
String *lsn;
String *walSegmentName;
} DbBackupStartResult;
DbBackupStartResult dbBackupStart(Db *this, bool startFast, bool stopAuto);
// Stop backup and return starting lsn, wal segment name, backup label, and tablspace map
typedef struct DbBackupStopResult
{
String *lsn;
String *walSegmentName;
String *backupLabel;
String *tablespaceMap;
} DbBackupStopResult;
DbBackupStopResult dbBackupStop(Db *this);
bool dbIsStandby(Db *this);
// Get list of databases in the cluster: select oid, datname, datlastsysoid from pg_database
VariantList *dbList(Db *this);
// Waits for replay on the standby to equal the target LSN
void dbReplayWait(Db *this, const String *targetLsn, TimeMSec timeout);
// Epoch time on the PostgreSQL host in ms
TimeMSec dbTimeMSec(Db *this);
// Get list of tablespaces in the cluster: select oid, datname, datlastsysoid from pg_database
VariantList *dbTablespaceList(Db *this);
String *dbWalSwitch(Db *this);
void dbClose(Db *this);

View File

@ -572,7 +572,7 @@ unit:
test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: db
total: 2
total: 3
containerReq: true
perlReq: true

View File

@ -134,6 +134,346 @@ Macros for defining groups of functions that implement various queries and comma
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_TIME_QUERY(sessionParam, timeParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, .param = "[\"select (extract(epoch from clock_timestamp()) * 1000)::bigint\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_INT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", \
.resultZ = strPtr(strNewFmt("%" PRId64, (int64_t)(timeParam)))}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_ADVISORY_LOCK(sessionParam, lockAcquiredParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, .param = "[\"select pg_catalog.pg_try_advisory_lock(12340078987004321)::bool\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_BOOL}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = cvtBoolToConstZ(lockAcquiredParam)}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_IS_IN_BACKUP(sessionParam, inBackupParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, .param = "[\"select pg_catalog.pg_is_in_backup()::bool\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_BOOL}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = cvtBoolToConstZ(inBackupParam)}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_START_BACKUP_83(sessionParam, lsnParam, walSegmentNameParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_xlogfile_name(lsn)::text as wal_segment_name\\n" \
" from pg_catalog.pg_start_backup('pgBackRest backup started at ' || current_timestamp) as lsn\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_START_BACKUP_84_95(sessionParam, startFastParam, lsnParam, walSegmentNameParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = strPtr(strNewFmt( \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_xlogfile_name(lsn)::text as wal_segment_name\\n" \
" from pg_catalog.pg_start_backup('pgBackRest backup started at ' || current_timestamp, %s) as lsn\"]", \
cvtBoolToConstZ(startFastParam))), \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_START_BACKUP_96(sessionParam, startFastParam, lsnParam, walSegmentNameParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = strPtr(strNewFmt( \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_xlogfile_name(lsn)::text as wal_segment_name\\n" \
" from pg_catalog.pg_start_backup('pgBackRest backup started at ' || current_timestamp, %s, false) as lsn\"]", \
cvtBoolToConstZ(startFastParam))), \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_START_BACKUP_GE_10(sessionParam, startFastParam, lsnParam, walSegmentNameParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = strPtr(strNewFmt( \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_walfile_name(lsn)::text as wal_segment_name\\n" \
" from pg_catalog.pg_start_backup('pgBackRest backup started at ' || current_timestamp, %s, false) as lsn\"]", \
cvtBoolToConstZ(startFastParam))), \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_STOP_BACKUP_LE_95(sessionParam, lsnParam, walSegmentNameParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_xlogfile_name(lsn)::text as wal_segment_name\\n" \
" from pg_catalog.pg_stop_backup() as lsn\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_STOP_BACKUP_96(sessionParam, lsnParam, walSegmentNameParam, tablespaceMapParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_xlogfile_name(lsn)::text as wal_segment_name,\\n" \
" labelfile::text as backuplabel_file,\\n" \
" spcmapfile::text as tablespacemap_file\\n" \
" from pg_catalog.pg_stop_backup(false)\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 4}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[2]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[3]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,2]", .resultZ = "BACKUP_LABEL_DATA"}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,3]", \
.resultZ = tablespaceMapParam ? "TABLESPACE_MAP_DATA" : "\n"}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_STOP_BACKUP_GE_10(sessionParam, lsnParam, walSegmentNameParam, tablespaceMapParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select lsn::text as lsn,\\n" \
" pg_catalog.pg_walfile_name(lsn)::text as wal_segment_name,\\n" \
" labelfile::text as backuplabel_file,\\n" \
" spcmapfile::text as tablespacemap_file\\n" \
" from pg_catalog.pg_stop_backup(false, false)\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 4}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[2]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[3]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = walSegmentNameParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,2]", .resultZ = "BACKUP_LABEL_DATA"}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,3]", \
.resultZ = tablespaceMapParam ? "TABLESPACE_MAP_DATA" : "\n"}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_DATABASE_LIST_1(sessionParam, databaseNameParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = "[\"select oid::oid, datname::text, datlastsysoid::oid from pg_catalog.pg_database\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 3}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_INT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[2]", .resultInt = HRNPQ_TYPE_INT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = STRINGIFY(16384)}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = databaseNameParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,2]", .resultZ = STRINGIFY(13777)}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_TABLESPACE_LIST_0(sessionParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, .param = "[\"select oid::oid, spcname::text from pg_catalog.pg_tablespace\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 0}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_INT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_TABLESPACE_LIST_1(sessionParam, id1Param, name1Param) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, .param = "[\"select oid::oid, spcname::text from pg_catalog.pg_tablespace\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_INT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = strPtr(strNewFmt("%d", id1Param))}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = name1Param}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_CHECKPOINT(sessionParam) \
{.session = sessionParam, .function = HRNPQ_SENDQUERY, .param = "[\"checkpoint\"]", .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_COMMAND_OK}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_REPLAY_TARGET_REACHED( \
sessionParam, walNameParam, lsnNameParam, targetLsnParam, targetReachedParam, replayLsnParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select replayLsn::text,\\n" \
" (replayLsn > '" targetLsnParam "')::bool as targetReached\\n" \
" from pg_catalog.pg_last_" walNameParam "_replay_" lsnNameParam "() as replayLsn\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_BOOL}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = replayLsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = cvtBoolToConstZ(targetReachedParam)}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_REPLAY_TARGET_REACHED_LE_96(sessionParam, targetLsnParam, targetReachedParam, reachedLsnParam) \
HRNPQ_MACRO_REPLAY_TARGET_REACHED(sessionParam, "xlog", "location", targetLsnParam, targetReachedParam, reachedLsnParam)
#define HRNPQ_MACRO_REPLAY_TARGET_REACHED_GE_10(sessionParam, targetLsnParam, targetReachedParam, reachedLsnParam) \
HRNPQ_MACRO_REPLAY_TARGET_REACHED(sessionParam, "wal", "lsn", targetLsnParam, targetReachedParam, reachedLsnParam)
#define HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED(sessionParam, lsnNameParam, targetLsnParam, targetReachedParam, checkpointLsnParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select (checkpoint_" lsnNameParam " > '" targetLsnParam "')::bool as targetReached,\\n" \
" checkpoint_" lsnNameParam "::text as checkpointLsn\\n" \
" from pg_catalog.pg_control_checkpoint()\"]", \
.resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 2}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_BOOL}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = cvtBoolToConstZ(targetReachedParam)}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = checkpointLsnParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED_96(sessionParam, targetLsnParam, targetReachedParam, checkpointLsnParam) \
HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED(sessionParam, "location", targetLsnParam, targetReachedParam, checkpointLsnParam)
#define HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED_GE_10(sessionParam, targetLsnParam, targetReachedParam, checkpointLsnParam) \
HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED(sessionParam, "lsn", targetLsnParam, targetReachedParam, checkpointLsnParam)
#define HRNPQ_MACRO_REPLAY_WAIT_LE_95(sessionParam, targetLsnParam) \
HRNPQ_MACRO_REPLAY_TARGET_REACHED_LE_96(sessionParam, targetLsnParam, true, "X/X"), \
HRNPQ_MACRO_CHECKPOINT(sessionParam)
#define HRNPQ_MACRO_REPLAY_WAIT_96(sessionParam, targetLsnParam) \
HRNPQ_MACRO_REPLAY_TARGET_REACHED_LE_96(sessionParam, targetLsnParam, true, "X/X"), \
HRNPQ_MACRO_CHECKPOINT(sessionParam), \
HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED_96(sessionParam, targetLsnParam, true, "X/X")
#define HRNPQ_MACRO_REPLAY_WAIT_GE_10(sessionParam, targetLsnParam) \
HRNPQ_MACRO_REPLAY_TARGET_REACHED_GE_10(sessionParam, targetLsnParam, true, "X/X"), \
HRNPQ_MACRO_CHECKPOINT(sessionParam), \
HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED_GE_10(sessionParam, targetLsnParam, true, "X/X")
#define HRNPQ_MACRO_CLOSE(sessionParam) \
{.session = sessionParam, .function = HRNPQ_FINISH}
@ -143,18 +483,22 @@ Macros for defining groups of functions that implement various queries and comma
/***********************************************************************************************************************************
Macros to simplify dbOpen() for specific database versions
***********************************************************************************************************************************/
#define HRNPQ_MACRO_OPEN_84(sessionParam, connectParam, pgPathParam, archiveMode, archiveCommand) \
#define HRNPQ_MACRO_OPEN_LE_91(sessionParam, connectParam, pgVersion, pgPathParam, archiveMode, archiveCommand) \
HRNPQ_MACRO_OPEN(sessionParam, connectParam), \
HRNPQ_MACRO_SET_SEARCH_PATH(sessionParam), \
HRNPQ_MACRO_VALIDATE_QUERY(sessionParam, PG_VERSION_84, pgPathParam, archiveMode, archiveCommand)
HRNPQ_MACRO_VALIDATE_QUERY(sessionParam, pgVersion, pgPathParam, archiveMode, archiveCommand)
#define HRNPQ_MACRO_OPEN_92(sessionParam, connectParam, pgPathParam, standbyParam, archiveMode, archiveCommand) \
#define HRNPQ_MACRO_OPEN_GE_92(sessionParam, connectParam, pgVersion, pgPathParam, standbyParam, archiveMode, archiveCommand) \
HRNPQ_MACRO_OPEN(sessionParam, connectParam), \
HRNPQ_MACRO_SET_SEARCH_PATH(sessionParam), \
HRNPQ_MACRO_VALIDATE_QUERY(sessionParam, PG_VERSION_92, pgPathParam, archiveMode, archiveCommand), \
HRNPQ_MACRO_VALIDATE_QUERY(sessionParam, pgVersion, pgPathParam, archiveMode, archiveCommand), \
HRNPQ_MACRO_SET_APPLICATION_NAME(sessionParam), \
HRNPQ_MACRO_IS_STANDBY_QUERY(sessionParam, standbyParam)
// ??? This is really just a special case of the above and should be replaced by it
#define HRNPQ_MACRO_OPEN_92(sessionParam, connectParam, pgPathParam, standbyParam, archiveMode, archiveCommand) \
HRNPQ_MACRO_OPEN_GE_92(sessionParam, connectParam, PG_VERSION_92, pgPathParam, standbyParam, archiveMode, archiveCommand)
/***********************************************************************************************************************************
Data type constants
***********************************************************************************************************************************/

View File

@ -8,6 +8,42 @@ Test Database
#include "common/io/handleRead.h"
#include "common/io/handleWrite.h"
#include "common/type/json.h"
/***********************************************************************************************************************************
Macro to check that replay is making progress -- this does not seem useful enough to be included in the pq harness header
***********************************************************************************************************************************/
#define HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS( \
sessionParam, walNameParam, lsnNameParam, targetLsnParam, targetReachedParam, replayLsnParam, replayLastLsnParam, \
replayProgressParam, sleepParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, \
.param = \
"[\"select replayLsn::text,\\n" \
" (replayLsn > '" targetLsnParam "')::bool as targetReached,\\n" \
" (replayLsn > '" replayLastLsnParam "')::bool as replayProgress\\n" \
" from pg_catalog.pg_last_" walNameParam "_replay_" lsnNameParam "() as replayLsn\"]", \
.resultInt = 1, .sleep = sleepParam}, \
{.session = sessionParam, .function = HRNPQ_CONSUMEINPUT}, \
{.session = sessionParam, .function = HRNPQ_ISBUSY}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT}, \
{.session = sessionParam, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, \
{.session = sessionParam, .function = HRNPQ_NTUPLES, .resultInt = 1}, \
{.session = sessionParam, .function = HRNPQ_NFIELDS, .resultInt = 3}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_BOOL}, \
{.session = sessionParam, .function = HRNPQ_FTYPE, .param = "[2]", .resultInt = HRNPQ_TYPE_BOOL}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = replayLsnParam}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = cvtBoolToConstZ(targetReachedParam)}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,2]", .resultZ = cvtBoolToConstZ(replayProgressParam)}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS_GE_10( \
sessionParam, targetLsnParam, targetReachedParam, replayLsnParam, replayLastLsnParam, replayProgressParam, sleepParam) \
HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS( \
sessionParam, "wal", "lsn", targetLsnParam, targetReachedParam, replayLsnParam, replayLastLsnParam, replayProgressParam, \
sleepParam)
/***********************************************************************************************************************************
Test Run
@ -86,7 +122,7 @@ testRun(void)
TEST_RESULT_VOID(dbOpen(db), "open db");
TEST_RESULT_VOID(dbFree(db), "free db");
// Open the database, but don't free it so the server is force to do it on shutdown
// Open the database, but don't free it so the server is forced to do it on shutdown
TEST_ASSIGN(db, dbNew(NULL, client, strNew("test")), "create db");
TEST_RESULT_VOID(dbOpen(db), "open db");
TEST_RESULT_STR(strPtr(dbWalSwitch(db)), "000000030000000200000003", " wal switch");
@ -99,6 +135,308 @@ testRun(void)
HARNESS_FORK_END();
}
// *****************************************************************************************************************************
if (testBegin("dbBackupStart(), dbBackupStop(), dbTime(), dbList(), dbTablespaceList(), and dbReplayWait()"))
{
StringList *argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--pg1-path=/pg1");
harnessCfgLoad(cfgCmdBackup, argList);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("PostgreSQL 8.3 start backup with no start fast");
harnessPqScriptSet((HarnessPq [])
{
// Connect to primary
HRNPQ_MACRO_OPEN_LE_91(1, "dbname='postgres' port=5432", PG_VERSION_83, "/pg1", NULL, NULL),
// Get advisory lock
HRNPQ_MACRO_ADVISORY_LOCK(1, true),
// Start backup with no start fast
HRNPQ_MACRO_START_BACKUP_83(1, "1/1", "000000010000000100000001"),
// Close primary
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
DbGetResult db = {.primaryId = 0};
TEST_ASSIGN(db, dbGet(true, true, false), "get primary");
TEST_RESULT_STR_Z(dbBackupStart(db.primary, false, false).lsn, "1/1", "start backup");
TEST_RESULT_VOID(dbFree(db.primary), "free primary");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("PostgreSQL 9.5 start/stop backup");
harnessPqScriptSet((HarnessPq [])
{
// Connect to primary
HRNPQ_MACRO_OPEN_GE_92(1, "dbname='postgres' port=5432", PG_VERSION_95, "/pg1", false, NULL, NULL),
// Get start time
HRNPQ_MACRO_TIME_QUERY(1, 1000),
// Start backup errors on advisory lock
HRNPQ_MACRO_ADVISORY_LOCK(1, false),
// Start backup
HRNPQ_MACRO_ADVISORY_LOCK(1, true),
HRNPQ_MACRO_IS_IN_BACKUP(1, false),
HRNPQ_MACRO_START_BACKUP_84_95(1, false, "2/3", "000000010000000200000003"),
HRNPQ_MACRO_DATABASE_LIST_1(1, "test1"),
HRNPQ_MACRO_TABLESPACE_LIST_0(1),
// Stop backup
HRNPQ_MACRO_STOP_BACKUP_LE_95(1, "2/4", "000000010000000200000004"),
// Close primary
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(db, dbGet(true, true, false), "get primary");
TEST_RESULT_UINT(dbTimeMSec(db.primary), 1000, "check time");
TEST_ERROR(
dbBackupStart(db.primary, false, false), LockAcquireError,
"unable to acquire pgBackRest advisory lock\n"
"HINT: is another pgBackRest backup already running on this cluster?");
DbBackupStartResult backupStartResult = {.lsn = NULL};
TEST_ASSIGN(backupStartResult, dbBackupStart(db.primary, false, true), "start backup");
TEST_RESULT_STR_Z(backupStartResult.lsn, "2/3", "check lsn");
TEST_RESULT_STR_Z(backupStartResult.walSegmentName, "000000010000000200000003", "check wal segment name");
TEST_RESULT_STR_Z(jsonFromVar(varNewVarLst(dbList(db.primary))), "[[16384,\"test1\",13777]]", "check db list");
TEST_RESULT_STR_Z(jsonFromVar(varNewVarLst(dbTablespaceList(db.primary))), "[]", "check tablespace list");
DbBackupStopResult backupStopResult = {.lsn = NULL};
TEST_ASSIGN(backupStopResult, dbBackupStop(db.primary), "stop backup");
TEST_RESULT_STR_Z(backupStopResult.lsn, "2/4", "check lsn");
TEST_RESULT_STR_Z(backupStopResult.walSegmentName, "000000010000000200000004", "check wal segment name");
TEST_RESULT_STR_Z(backupStopResult.backupLabel, NULL, "check backup label is not set");
TEST_RESULT_STR_Z(backupStopResult.tablespaceMap, NULL, "check tablespace map is not set");
TEST_RESULT_VOID(dbFree(db.primary), "free primary");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("PostgreSQL 9.5 start/stop backup where backup is in progress");
harnessPqScriptSet((HarnessPq [])
{
// Connect to primary
HRNPQ_MACRO_OPEN_GE_92(1, "dbname='postgres' port=5432", PG_VERSION_95, "/pg1", false, NULL, NULL),
// Start backup when backup is in progress
HRNPQ_MACRO_ADVISORY_LOCK(1, true),
HRNPQ_MACRO_IS_IN_BACKUP(1, true),
// Stop old backup
HRNPQ_MACRO_STOP_BACKUP_LE_95(1, "1/1", "000000010000000100000001"),
// Start backup
HRNPQ_MACRO_START_BACKUP_84_95(1, true, "2/5", "000000010000000200000005"),
// Stop backup
HRNPQ_MACRO_STOP_BACKUP_LE_95(1, "2/6", "000000010000000200000006"),
// Close primary
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(db, dbGet(true, true, false), "get primary");
TEST_RESULT_STR_Z(dbBackupStart(db.primary, true, true).lsn, "2/5", "start backup");
TEST_RESULT_LOG(
"P00 WARN: the cluster is already in backup mode but no pgBackRest backup process is running."
" pg_stop_backup() will be called so a new backup can be started.");
TEST_RESULT_STR_Z(dbBackupStop(db.primary).lsn, "2/6", "stop backup");
TEST_RESULT_VOID(dbFree(db.primary), "free primary");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("PostgreSQL 9.6 start/stop backup");
harnessPqScriptSet((HarnessPq [])
{
// Connect to primary
HRNPQ_MACRO_OPEN_GE_92(1, "dbname='postgres' port=5432", PG_VERSION_96, "/pg1", false, NULL, NULL),
// Start backup
HRNPQ_MACRO_ADVISORY_LOCK(1, true),
HRNPQ_MACRO_START_BACKUP_96(1, false, "3/3", "000000010000000300000003"),
// Stop backup
HRNPQ_MACRO_STOP_BACKUP_96(1, "3/4", "000000010000000300000004", false),
// Close primary
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(db, dbGet(true, true, false), "get primary");
TEST_ASSIGN(backupStartResult, dbBackupStart(db.primary, false, false), "start backup");
TEST_RESULT_STR_Z(backupStartResult.lsn, "3/3", "check lsn");
TEST_RESULT_STR_Z(backupStartResult.walSegmentName, "000000010000000300000003", "check wal segment name");
TEST_ASSIGN(backupStopResult, dbBackupStop(db.primary), "stop backup");
TEST_RESULT_STR_Z(backupStopResult.lsn, "3/4", "check lsn");
TEST_RESULT_STR_Z(backupStopResult.walSegmentName, "000000010000000300000004", "check wal segment name");
TEST_RESULT_STR_Z(backupStopResult.backupLabel, "BACKUP_LABEL_DATA", "check backup label");
TEST_RESULT_STR_Z(backupStopResult.tablespaceMap, NULL, "check tablespace map is not set");
TEST_RESULT_VOID(dbFree(db.primary), "free primary");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("PostgreSQL 9.5 start backup from standby");
argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--pg1-path=/pg1");
strLstAddZ(argList, "--pg2-path=/pg2");
strLstAddZ(argList, "--pg2-port=5433");
harnessCfgLoad(cfgCmdBackup, argList);
harnessPqScriptSet((HarnessPq [])
{
// Connect to primary
HRNPQ_MACRO_OPEN_GE_92(1, "dbname='postgres' port=5432", PG_VERSION_95, "/pg1", false, NULL, NULL),
// Connect to standby
HRNPQ_MACRO_OPEN_GE_92(2, "dbname='postgres' port=5433", PG_VERSION_95, "/pg2", true, NULL, NULL),
// Start backup
HRNPQ_MACRO_ADVISORY_LOCK(1, true),
HRNPQ_MACRO_START_BACKUP_84_95(1, false, "5/4", "000000050000000500000004"),
// Wait for standby to sync
HRNPQ_MACRO_REPLAY_WAIT_LE_95(2, "5/4"),
// Close standby
HRNPQ_MACRO_CLOSE(2),
// Close primary
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(db, dbGet(false, true, true), "get primary and standby");
TEST_RESULT_STR_Z(dbBackupStart(db.primary, false, false).lsn, "5/4", "start backup");
TEST_RESULT_VOID(dbReplayWait(db.standby, STRDEF("5/4"), 1000), "sync standby");
TEST_RESULT_VOID(dbFree(db.standby), "free standby");
TEST_RESULT_VOID(dbFree(db.primary), "free primary");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("PostgreSQL 10 start/stop backup from standby");
harnessPqScriptSet((HarnessPq [])
{
// Connect to primary
HRNPQ_MACRO_OPEN_GE_92(1, "dbname='postgres' port=5432", PG_VERSION_10, "/pg1", false, NULL, NULL),
// Connect to standby
HRNPQ_MACRO_OPEN_GE_92(2, "dbname='postgres' port=5433", PG_VERSION_10, "/pg2", true, NULL, NULL),
// Start backup
HRNPQ_MACRO_ADVISORY_LOCK(1, true),
HRNPQ_MACRO_START_BACKUP_GE_10(1, false, "5/5", "000000050000000500000005"),
// Standby returns NULL lsn
{.session = 2,
.function = HRNPQ_SENDQUERY,
.param =
"[\"select replayLsn::text,\\n"
" (replayLsn > '5/5')::bool as targetReached\\n"
" from pg_catalog.pg_last_wal_replay_lsn() as replayLsn\"]",
.resultInt = 1},
{.session = 2, .function = HRNPQ_CONSUMEINPUT},
{.session = 2, .function = HRNPQ_ISBUSY},
{.session = 2, .function = HRNPQ_GETRESULT},
{.session = 2, .function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK},
{.session = 2, .function = HRNPQ_NTUPLES, .resultInt = 1},
{.session = 2, .function = HRNPQ_NFIELDS, .resultInt = 2},
{.session = 2, .function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_TEXT},
{.session = 2, .function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_BOOL},
{.session = 2, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = ""},
{.session = 2, .function = HRNPQ_GETISNULL, .param = "[0,0]", .resultInt = 1},
{.session = 2, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = "false"},
{.session = 2, .function = HRNPQ_CLEAR},
{.session = 2, .function = HRNPQ_GETRESULT, .resultNull = true},
// Timeout waiting for sync
HRNPQ_MACRO_REPLAY_TARGET_REACHED_GE_10(2, "5/5", false, "5/3"),
HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS_GE_10(2, "5/5", false, "5/3", "5/3", false, 250),
HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS_GE_10(2, "5/5", false, "5/3", "5/3", false, 0),
// Checkpoint target not reached
HRNPQ_MACRO_REPLAY_TARGET_REACHED_GE_10(2, "5/5", true, "5/5"),
HRNPQ_MACRO_CHECKPOINT(2),
HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED_GE_10(2, "5/5", false, "5/4"),
// Wait for standby to sync
HRNPQ_MACRO_REPLAY_TARGET_REACHED_GE_10(2, "5/5", false, "5/3"),
HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS_GE_10(2, "5/5", false, "5/3", "5/3", false, 0),
HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS_GE_10(2, "5/5", false, "5/4", "5/3", true, 0),
HRNPQ_MACRO_REPLAY_TARGET_REACHED_PROGRESS_GE_10(2, "5/5", true, "5/5", "5/4", true, 0),
HRNPQ_MACRO_CHECKPOINT(2),
HRNPQ_MACRO_CHECKPOINT_TARGET_REACHED_GE_10(2, "5/5", true, "X/X"),
// Close standby
HRNPQ_MACRO_CLOSE(2),
// Stop backup
HRNPQ_MACRO_STOP_BACKUP_GE_10(1, "5/6", "000000050000000500000006", true),
// Close primary
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(db, dbGet(false, true, true), "get primary and standby");
TEST_RESULT_STR_Z(dbBackupStart(db.primary, false, false).lsn, "5/5", "start backup");
TEST_ERROR(
dbReplayWait(db.standby, STRDEF("5/5"), 1000), ArchiveTimeoutError,
"unable to query replay lsn on the standby using 'pg_catalog.pg_last_wal_replay_lsn()'\n"
"HINT: Is this a standby?");
TEST_ERROR(
dbReplayWait(db.standby, STRDEF("5/5"), 200), ArchiveTimeoutError,
"timeout before standby replayed to 5/5 - only reached 5/3");
TEST_ERROR(
dbReplayWait(db.standby, STRDEF("5/5"), 1000), ArchiveTimeoutError,
"the checkpoint lsn 5/4 is less than the target lsn 5/5 even though the replay lsn is 5/5");
TEST_RESULT_VOID(dbReplayWait(db.standby, STRDEF("5/5"), 1000), "sync standby");
TEST_RESULT_VOID(dbFree(db.standby), "free standby");
TEST_RESULT_STR_Z(dbBackupStop(db.primary).tablespaceMap, "TABLESPACE_MAP_DATA", "stop backup");
TEST_RESULT_VOID(dbFree(db.primary), "free primary");
}
// *****************************************************************************************************************************
if (testBegin("dbGet()"))
{
@ -160,7 +498,7 @@ testRun(void)
// -------------------------------------------------------------------------------------------------------------------------
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_84(1, "dbname='postgres' port=5432", "/pgdata", NULL, NULL),
HRNPQ_MACRO_OPEN_LE_91(1, "dbname='postgres' port=5432", PG_VERSION_84, "/pgdata", NULL, NULL),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
@ -188,8 +526,8 @@ testRun(void)
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_84(1, "dbname='postgres' port=5432", "/pgdata", NULL, NULL),
HRNPQ_MACRO_OPEN_84(8, "dbname='postgres' port=5433", "/pgdata", NULL, NULL),
HRNPQ_MACRO_OPEN_LE_91(1, "dbname='postgres' port=5432", PG_VERSION_84, "/pgdata", NULL, NULL),
HRNPQ_MACRO_OPEN_LE_91(8, "dbname='postgres' port=5433", PG_VERSION_84, "/pgdata", NULL, NULL),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_CLOSE(8),