1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-18 04:58:51 +02:00

Add Db object to encapsulate PostgreSQL queries and commands.

Migrate functionality from the Perl Db module to C. For now this is just enough to implement the WAL switch check.

Add the dbGet() helper function to get Db objects easily.

Create macros in harnessPq to make writing pq scripts easier by grouping commonly used functions together.

Reviewed by Cynthia Shang.
This commit is contained in:
David Steele 2019-08-01 15:38:27 -04:00
parent f9e1f3a798
commit e4901d50d5
20 changed files with 1180 additions and 12 deletions

View File

@ -70,6 +70,14 @@
<p>Add Perl interface to C storage layer.</p>
</release-item>
<release-item>
<release-item-contributor-list>
<release-item-reviewer id="cynthia.shang"/>
</release-item-contributor-list>
<p>Add <code>Db</code> object to encapsulate <postgres/> queries and commands.</p>
</release-item>
<release-item>
<release-item-contributor-list>
<release-item-reviewer id="cynthia.shang"/>

View File

@ -120,6 +120,9 @@ SRCS = \
config/load.c \
config/parse.c \
config/protocol.c \
db/db.c \
db/helper.c \
db/protocol.c \
info/info.c \
info/infoArchive.c \
info/infoBackup.c \
@ -249,7 +252,7 @@ command/info/info.o: command/info/info.c build.auto.h command/archive/common.h c
command/local/local.o: command/local/local.c build.auto.h command/archive/get/protocol.h command/archive/push/protocol.h command/backup/protocol.h command/restore/protocol.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/handleRead.h common/io/handleWrite.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/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/helper.h protocol/server.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/local/local.c -o command/local/local.o
command/remote/remote.o: command/remote/remote.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/handleRead.h common/io/handleWrite.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/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/helper.h protocol/server.h storage/remote/protocol.h
command/remote/remote.o: command/remote/remote.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/handleRead.h common/io/handleWrite.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/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 db/protocol.h protocol/client.h protocol/command.h protocol/helper.h protocol/server.h storage/remote/protocol.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/remote/remote.c -o command/remote/remote.o
command/restore/file.o: command/restore/file.c build.auto.h command/restore/file.h common/assert.h common/compress/gzip/common.h common/compress/gzip/decompress.h common/crypto/cipherBlock.h common/crypto/common.h common/crypto/hash.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/filter/size.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/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 storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h
@ -426,6 +429,15 @@ 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/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/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 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/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 version.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c db/helper.c -o db/helper.o
db/protocol.o: db/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/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/protocol.h postgres/client.h postgres/interface.h protocol/client.h protocol/command.h protocol/server.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c db/protocol.c -o db/protocol.o
info/info.o: info/info.c build.auto.h common/assert.h common/crypto/cipherBlock.h common/crypto/common.h common/crypto/hash.h common/debug.h common/error.auto.h common/error.h common/ini.h common/io/filter/filter.h common/io/filter/filter.intern.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/json.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h info/info.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h version.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c info/info.c -o info/info.o

View File

@ -11,6 +11,7 @@ Remote Command
#include "common/log.h"
#include "config/config.h"
#include "config/protocol.h"
#include "db/protocol.h"
#include "protocol/helper.h"
#include "protocol/server.h"
#include "storage/remote/protocol.h"
@ -33,6 +34,7 @@ cmdRemote(int handleRead, int handleWrite)
ProtocolServer *server = protocolServerNew(name, PROTOCOL_SERVICE_REMOTE_STR, read, write);
protocolServerHandlerAdd(server, storageRemoteProtocol);
protocolServerHandlerAdd(server, dbProtocol);
protocolServerHandlerAdd(server, configProtocol);
// Acquire a lock if this command needs one. We'll use the noop that is always sent from the client right after the

313
src/db/db.c Normal file
View File

@ -0,0 +1,313 @@
/***********************************************************************************************************************************
Database Client
***********************************************************************************************************************************/
#include "build.auto.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/memContext.h"
#include "common/object.h"
#include "db/db.h"
#include "db/protocol.h"
#include "postgres/interface.h"
#include "postgres/version.h"
#include "version.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
struct Db
{
MemContext *memContext;
PgClient *client; // Local PostgreSQL client
ProtocolClient *remoteClient; // Protocol client for remote db queries
unsigned int remoteIdx; // Index provided by the remote on open for subsequent calls
const String *applicationName; // Used to identify this connection in PostgreSQL
unsigned int pgVersion; // Version as reported by the database
const String *pgDataPath; // Data directory reported by the database
};
OBJECT_DEFINE_FREE(DB);
/***********************************************************************************************************************************
Close protocol connection. No need to close a locally created PgClient since it has its own destructor.
***********************************************************************************************************************************/
OBJECT_DEFINE_FREE_RESOURCE_BEGIN(DB, LOG, logLevelTrace)
{
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_DB_CLOSE_STR);
protocolCommandParamAdd(command, VARUINT(this->remoteIdx));
protocolClientExecute(this->remoteClient, command, false);
}
OBJECT_DEFINE_FREE_RESOURCE_END(LOG);
/***********************************************************************************************************************************
Create object
***********************************************************************************************************************************/
Db *
dbNew(PgClient *client, ProtocolClient *remoteClient, const String *applicationName)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(PG_CLIENT, client);
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, remoteClient);
FUNCTION_LOG_PARAM(STRING, applicationName);
FUNCTION_LOG_END();
ASSERT((client != NULL && remoteClient == NULL) || (client == NULL && remoteClient != NULL));
ASSERT(applicationName != NULL);
Db *this = NULL;
MEM_CONTEXT_NEW_BEGIN("Db")
{
this = memNew(sizeof(Db));
this->memContext = memContextCurrent();
this->client = pgClientMove(client, this->memContext);
this->remoteClient = remoteClient;
this->applicationName = strDup(applicationName);
}
MEM_CONTEXT_NEW_END();
FUNCTION_LOG_RETURN(DB, this);
}
/***********************************************************************************************************************************
Execute a query
***********************************************************************************************************************************/
static VariantList *
dbQuery(Db *this, const String *query)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(STRING, query);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(query != NULL);
VariantList *result = NULL;
// Query remotely
if (this->remoteClient != NULL)
{
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_DB_QUERY_STR);
protocolCommandParamAdd(command, VARUINT(this->remoteIdx));
protocolCommandParamAdd(command, VARSTR(query));
result = varVarLst(protocolClientExecute(this->remoteClient, command, true));
}
// Else locally
else
result = pgClientQuery(this->client, query);
FUNCTION_LOG_RETURN(VARIANT_LIST, result);
}
/***********************************************************************************************************************************
Execute a command that expects no output
***********************************************************************************************************************************/
static void
dbExec(Db *this, const String *command)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(STRING, command);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(command != NULL);
CHECK(dbQuery(this, command) == NULL);
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Execute a query that returns a single row and column
***********************************************************************************************************************************/
static Variant *
dbQueryColumn(Db *this, const String *query)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(STRING, query);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(query != NULL);
VariantList *result = dbQuery(this, query);
CHECK(varLstSize(result) == 1);
CHECK(varLstSize(varVarLst(varLstGet(result, 0))) == 1);
FUNCTION_LOG_RETURN(VARIANT, varLstGet(varVarLst(varLstGet(result, 0)), 0));
}
/***********************************************************************************************************************************
Execute a query that returns a single row
***********************************************************************************************************************************/
static VariantList *
dbQueryRow(Db *this, const String *query)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(STRING, query);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(query != NULL);
VariantList *result = dbQuery(this, query);
CHECK(varLstSize(result) == 1);
FUNCTION_LOG_RETURN(VARIANT_LIST, varVarLst(varLstGet(result, 0)));
}
/***********************************************************************************************************************************
Open the db connection
***********************************************************************************************************************************/
void
dbOpen(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
MEM_CONTEXT_TEMP_BEGIN()
{
// Open the connection
if (this->remoteClient != NULL)
{
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_DB_OPEN_STR);
this->remoteIdx = varUIntForce(protocolClientExecute(this->remoteClient, command, true));
// Set a callback to notify the remote when a connection is closed
memContextCallbackSet(this->memContext, dbFreeResource, this);
}
else
pgClientOpen(this->client);
// Set search_path to prevent overrides of the functions we expect to call. All queries should also be schema-qualified,
// but this is an extra level protection.
dbExec(this, STRDEF("set search_path = 'pg_catalog'"));
// Query the version and data_directory
VariantList *row = dbQueryRow(
this,
STRDEF(
"select (select setting from pg_catalog.pg_settings where name = 'server_version_num')::int4,"
" (select setting from pg_catalog.pg_settings where name = 'data_directory')::text"));
// Strip the minor version off since we don't need it. In the future it might be a good idea to warn users when they are
// running an old minor version.
this->pgVersion = varUIntForce(varLstGet(row, 0)) / 100 * 100;
// Store the data directory that PostgreSQL is running in. This can be compared to the configured pgBackRest directory when
// validating the configuration.
MEM_CONTEXT_BEGIN(this->memContext)
{
this->pgDataPath = strDup(varStr(varLstGet(row, 1)));
}
MEM_CONTEXT_END();
if (this->pgVersion >= PG_VERSION_APPLICATION_NAME)
dbExec(this, strNewFmt("set application_name = '%s'", strPtr(this->applicationName)));
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Is this instance a standby?
***********************************************************************************************************************************/
bool
dbIsStandby(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
bool result = false;
if (this->pgVersion >= PG_VERSION_HOT_STANDBY)
{
result = varBool(dbQueryColumn(this, STRDEF("select pg_catalog.pg_is_in_recovery()")));
}
FUNCTION_LOG_RETURN(BOOL, result);
}
/***********************************************************************************************************************************
Switch the WAL segment and return the segment that should have been archived
***********************************************************************************************************************************/
String *
dbWalSwitch(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
String *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
// Create a restore point to ensure current WAL will be archived. For versions < 9.1 activity will need to be generated by
// the user if there have been no writes since the last WAL switch.
if (this->pgVersion >= PG_VERSION_RESTORE_POINT)
dbQueryColumn(this, STRDEF("select pg_catalog.pg_create_restore_point('" PROJECT_NAME " Archive Check')::text"));
// Request a WAL segment switch
const char *walName = strPtr(pgWalName(this->pgVersion));
const String *walFileName = varStr(
dbQueryColumn(this, strNewFmt("select pg_catalog.pg_%sfile_name(pg_catalog.pg_switch_%s())::text", walName, walName)));
// Copy WAL segment name to the calling context
memContextSwitch(MEM_CONTEXT_OLD());
result = strDup(walFileName);
memContextSwitch(MEM_CONTEXT_TEMP());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(STRING, result);
}
/***********************************************************************************************************************************
Move the object to a new context
***********************************************************************************************************************************/
Db *
dbMove(Db *this, MemContext *parentNew)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(DB, this);
FUNCTION_TEST_PARAM(MEM_CONTEXT, parentNew);
FUNCTION_TEST_END();
ASSERT(parentNew != NULL);
if (this != NULL)
memContextMove(this->memContext, parentNew);
FUNCTION_TEST_RETURN(this);
}
/***********************************************************************************************************************************
Render as string for logging
***********************************************************************************************************************************/
String *
dbToLog(const Db *this)
{
return strNewFmt(
"{client: %s, remoteClient: %s}", this->client == NULL ? "null" : strPtr(pgClientToLog(this->client)),
this->remoteClient == NULL ? "null" : strPtr(protocolClientToLog(this->remoteClient)));
}

51
src/db/db.h Normal file
View File

@ -0,0 +1,51 @@
/***********************************************************************************************************************************
Database Client
Implements the required PostgreSQL queries and commands. Notice that there is no general purpose query function -- all queries are
expected to be embedded in this object.
***********************************************************************************************************************************/
#ifndef DB_DB_H
#define DB_DB_H
#include "postgres/client.h"
#include "protocol/client.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
#define DB_TYPE Db
#define DB_PREFIX db
typedef struct Db Db;
/***********************************************************************************************************************************
Constructor
***********************************************************************************************************************************/
Db *dbNew(PgClient *client, ProtocolClient *remoteClient, const String *applicationName);
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void dbOpen(Db *this);
bool dbIsStandby(Db *this);
String *dbWalSwitch(Db *this);
void dbClose(Db *this);
Db *dbMove(Db *this, MemContext *parentNew);
/***********************************************************************************************************************************
Destructor
***********************************************************************************************************************************/
void dbFree(Db *this);
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
String *dbToLog(const Db *this);
#define FUNCTION_LOG_DB_TYPE \
Db *
#define FUNCTION_LOG_DB_FORMAT(value, buffer, bufferSize) \
FUNCTION_LOG_STRING_OBJECT_FORMAT(value, dbToLog, buffer, bufferSize)
#endif

127
src/db/helper.c Normal file
View File

@ -0,0 +1,127 @@
/***********************************************************************************************************************************
Database Helper
***********************************************************************************************************************************/
#include "build.auto.h"
#include "common/debug.h"
#include "config/config.h"
#include "db/helper.h"
#include "postgres/interface.h"
#include "protocol/helper.h"
#include "version.h"
/***********************************************************************************************************************************
Get specified cluster
***********************************************************************************************************************************/
static Db *
dbGetId(unsigned int pgId)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(UINT, pgId);
FUNCTION_LOG_END();
ASSERT(pgId > 0);
Db *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
const String *applicationName = strNewFmt(PROJECT_NAME " [%s]", cfgCommandName(cfgCommand()));
if (pgIsLocal(pgId))
{
result = dbNew(
pgClientNew(
cfgOptionStr(cfgOptPgSocketPath + pgId - 1), cfgOptionUInt(cfgOptPgPort + pgId - 1), PG_DB_POSTGRES_STR, NULL,
(TimeMSec)(cfgOptionDbl(cfgOptDbTimeout) * MSEC_PER_SEC)),
NULL, applicationName);
}
else
result = dbNew(NULL, protocolRemoteGet(protocolStorageTypePg, pgId), applicationName);
dbMove(result, MEM_CONTEXT_OLD());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(DB, result);
}
/***********************************************************************************************************************************
Get primary cluster or primary and standby cluster
***********************************************************************************************************************************/
DbGetResult
dbGet(bool primaryOnly, bool primaryRequired)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(BOOL, primaryOnly);
FUNCTION_LOG_PARAM(BOOL, primaryRequired);
FUNCTION_LOG_END();
DbGetResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
// Loop through to look for primary and standby (if required)
for (unsigned int pgIdx = 0; pgIdx < cfgOptionIndexTotal(cfgOptPgPath); pgIdx++)
{
if (cfgOptionTest(cfgOptPgHost + pgIdx) || cfgOptionTest(cfgOptPgPath + pgIdx))
{
Db *db = NULL;
bool standby = false;
TRY_BEGIN()
{
db = dbGetId(pgIdx + 1);
dbOpen(db);
standby = dbIsStandby(db);
}
CATCH_ANY()
{
dbFree(db);
db = NULL;
LOG_WARN("unable to check pg-%u: [%s] %s", pgIdx + 1, errorTypeName(errorType()), errorMessage());
}
TRY_END();
// Was the connection successful
if (db != NULL)
{
// Is this cluster a standby
if (standby)
{
// If a standby has not already been found then assign it
if (result.standbyId == 0 && !primaryOnly)
{
result.standbyId = pgIdx + 1;
result.standby = db;
}
// Else close the connection since we don't need it
else
dbFree(db);
}
// Else is a primary
else
{
// Error if more than one primary was found
if (result.primaryId != 0)
THROW(DbConnectError, "more than one primary cluster found");
result.primaryId = pgIdx + 1;
result.primary = db;
}
}
}
}
// Error if no primary was found
if (result.primaryId == 0 && primaryRequired)
THROW(DbConnectError, "unable to find primary cluster - cannot proceed");
dbMove(result.primary, MEM_CONTEXT_OLD());
dbMove(result.standby, MEM_CONTEXT_OLD());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(DB_GET_RESULT, result);
}

34
src/db/helper.h Normal file
View File

@ -0,0 +1,34 @@
/***********************************************************************************************************************************
Database Helper
Helper functions for getting connections to PostgreSQL.
***********************************************************************************************************************************/
#ifndef DB_HELPER_H
#define DB_HELPER_H
#include <stdbool.h>
#include "db/db.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
typedef struct DbGetResult
{
unsigned int primaryId;
Db *primary;
unsigned int standbyId;
Db *standby;
} DbGetResult;
DbGetResult dbGet(bool primaryOnly, bool primaryRequired);
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
#define FUNCTION_LOG_DB_GET_RESULT_TYPE \
DbGetResult
#define FUNCTION_LOG_DB_GET_RESULT_FORMAT(value, buffer, bufferSize) \
objToLog(&value, "DbGetResult", buffer, bufferSize)
#endif

96
src/db/protocol.c Normal file
View File

@ -0,0 +1,96 @@
/***********************************************************************************************************************************
Db Protocol Handler
***********************************************************************************************************************************/
#include "build.auto.h"
#include "common/debug.h"
#include "common/io/io.h"
#include "common/log.h"
#include "common/memContext.h"
#include "common/type/list.h"
#include "config/config.h"
#include "db/protocol.h"
#include "postgres/client.h"
#include "postgres/interface.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
STRING_EXTERN(PROTOCOL_COMMAND_DB_OPEN_STR, PROTOCOL_COMMAND_DB_OPEN);
STRING_EXTERN(PROTOCOL_COMMAND_DB_QUERY_STR, PROTOCOL_COMMAND_DB_QUERY);
STRING_EXTERN(PROTOCOL_COMMAND_DB_CLOSE_STR, PROTOCOL_COMMAND_DB_CLOSE);
/***********************************************************************************************************************************
Local variables
***********************************************************************************************************************************/
static struct
{
List *pgClientList; // List of db objects
} dbProtocolLocal;
/***********************************************************************************************************************************
Process db protocol requests
***********************************************************************************************************************************/
bool
dbProtocol(const String *command, const VariantList *paramList, ProtocolServer *server)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, command);
FUNCTION_LOG_PARAM(VARIANT_LIST, paramList);
FUNCTION_LOG_PARAM(PROTOCOL_SERVER, server);
FUNCTION_LOG_END();
ASSERT(command != NULL);
// Attempt to satisfy the request -- we may get requests that are meant for other handlers
bool found = true;
MEM_CONTEXT_TEMP_BEGIN()
{
if (strEq(command, PROTOCOL_COMMAND_DB_OPEN_STR))
{
// If the db list does not exist then create it in the calling context (which should be persistent)
if (dbProtocolLocal.pgClientList == NULL)
{
memContextSwitch(MEM_CONTEXT_OLD());
dbProtocolLocal.pgClientList = lstNew(sizeof(PgClient *));
memContextSwitch(MEM_CONTEXT_TEMP());
}
// Add db to the list
unsigned int dbIdx = lstSize(dbProtocolLocal.pgClientList);
MEM_CONTEXT_BEGIN(lstMemContext(dbProtocolLocal.pgClientList))
{
// Only a single db is passed to the remote
PgClient *pgClient = pgClientNew(
cfgOptionStr(cfgOptPgSocketPath), cfgOptionUInt(cfgOptPgPort), PG_DB_POSTGRES_STR, NULL,
(TimeMSec)(cfgOptionDbl(cfgOptDbTimeout) * MSEC_PER_SEC));
pgClientOpen(pgClient);
lstAdd(dbProtocolLocal.pgClientList, &pgClient);
}
MEM_CONTEXT_END();
// Return db index which should be included in subsequent calls
protocolServerResponse(server, VARUINT(dbIdx));
}
else if (strEq(command, PROTOCOL_COMMAND_DB_QUERY_STR) || strEq(command, PROTOCOL_COMMAND_DB_CLOSE_STR))
{
PgClient *pgClient = *(PgClient **)lstGet(dbProtocolLocal.pgClientList, varUIntForce(varLstGet(paramList, 0)));
if (strEq(command, PROTOCOL_COMMAND_DB_QUERY_STR))
protocolServerResponse(server, varNewVarLst(pgClientQuery(pgClient, varStr(varLstGet(paramList, 1)))));
else
{
pgClientClose(pgClient);
protocolServerResponse(server, NULL);
}
}
else
found = false;
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(BOOL, found);
}

27
src/db/protocol.h Normal file
View File

@ -0,0 +1,27 @@
/***********************************************************************************************************************************
Db Protocol Handler
***********************************************************************************************************************************/
#ifndef DB_PROTOCOL_H
#define DB_PROTOCOL_H
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "protocol/client.h"
#include "protocol/server.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
#define PROTOCOL_COMMAND_DB_OPEN "dbOpen"
STRING_DECLARE(PROTOCOL_COMMAND_DB_OPEN_STR);
#define PROTOCOL_COMMAND_DB_QUERY "dbQuery"
STRING_DECLARE(PROTOCOL_COMMAND_DB_QUERY_STR);
#define PROTOCOL_COMMAND_DB_CLOSE "dbClose"
STRING_DECLARE(PROTOCOL_COMMAND_DB_CLOSE_STR);
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
bool dbProtocol(const String *command, const VariantList *paramList, ProtocolServer *server);
#endif

View File

@ -9,10 +9,7 @@ Postgres Client
#include "common/log.h"
#include "common/memContext.h"
#include "common/object.h"
#include "common/time.h"
#include "common/type/list.h"
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "common/wait.h"
#include "postgres/client.h"
@ -350,15 +347,36 @@ pgClientClose(PgClient *this)
FUNCTION_LOG_END();
ASSERT(this != NULL);
CHECK(this->connection != NULL);
memContextCallbackClear(this->memContext);
PQfinish(this->connection);
this->connection = NULL;
if (this->connection != NULL)
{
memContextCallbackClear(this->memContext);
PQfinish(this->connection);
this->connection = NULL;
}
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Move the pg client object to a new context
***********************************************************************************************************************************/
PgClient *
pgClientMove(PgClient *this, MemContext *parentNew)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(PG_CLIENT, this);
FUNCTION_TEST_PARAM(MEM_CONTEXT, parentNew);
FUNCTION_TEST_END();
ASSERT(parentNew != NULL);
if (this != NULL)
memContextMove(this->memContext, parentNew);
FUNCTION_TEST_RETURN(this);
}
/***********************************************************************************************************************************
Render as string for logging
***********************************************************************************************************************************/

View File

@ -8,6 +8,10 @@ casts to queries to output one of these types.
#ifndef POSTGRES_QUERY_H
#define POSTGRES_QUERY_H
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "common/time.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
@ -29,6 +33,8 @@ PgClient *pgClientOpen(PgClient *this);
VariantList *pgClientQuery(PgClient *this, const String *query);
void pgClientClose(PgClient *this);
PgClient *pgClientMove(PgClient *this, MemContext *parentNew);
/***********************************************************************************************************************************
Destructor
***********************************************************************************************************************************/

View File

@ -14,6 +14,12 @@ PostgreSQL Interface
#include "postgres/version.h"
#include "storage/helper.h"
/***********************************************************************************************************************************
Defines for various Postgres paths and files
***********************************************************************************************************************************/
STRING_EXTERN(PG_NAME_WAL_STR, PG_NAME_WAL);
STRING_EXTERN(PG_NAME_XLOG_STR, PG_NAME_XLOG);
/***********************************************************************************************************************************
Define default wal segment size
@ -35,6 +41,11 @@ something far larger needed but <= the minimum read size on just about any syste
***********************************************************************************************************************************/
#define PG_WAL_HEADER_SIZE ((unsigned int)(512))
/***********************************************************************************************************************************
Name of default PostgreSQL database used for running all queries and commands
***********************************************************************************************************************************/
STRING_EXTERN(PG_DB_POSTGRES_STR, PG_DB_POSTGRES);
/***********************************************************************************************************************************
PostgreSQL interface definitions
@ -407,6 +418,19 @@ pgWalFromFile(const String *walFile)
FUNCTION_LOG_RETURN(PG_WAL, result);
}
/***********************************************************************************************************************************
Get WAL name (wal/xlog) for a PostgreSQL version
***********************************************************************************************************************************/
const String *
pgWalName(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(pgVersion >= PG_VERSION_WAL_RENAME ? PG_NAME_WAL_STR : PG_NAME_XLOG_STR);
}
/***********************************************************************************************************************************
Create pg_control for testing
***********************************************************************************************************************************/

View File

@ -17,6 +17,17 @@ Defines for various Postgres paths and files
#define PG_PATH_ARCHIVE_STATUS "archive_status"
#define PG_PATH_GLOBAL "global"
#define PG_NAME_WAL "wal"
STRING_DECLARE(PG_NAME_WAL_STR);
#define PG_NAME_XLOG "xlog"
STRING_DECLARE(PG_NAME_XLOG_STR);
/***********************************************************************************************************************************
Name of default PostgreSQL database used for running all queries and commands
***********************************************************************************************************************************/
#define PG_DB_POSTGRES "postgres"
STRING_DECLARE(PG_DB_POSTGRES_STR);
/***********************************************************************************************************************************
Define default page size
@ -69,6 +80,8 @@ String *pgVersionToStr(unsigned int version);
PgWal pgWalFromFile(const String *walFile);
PgWal pgWalFromBuffer(const Buffer *walBuffer);
const String *pgWalName(unsigned int pgVersion);
/***********************************************************************************************************************************
Test Functions
***********************************************************************************************************************************/

View File

@ -26,6 +26,21 @@ PostgreSQL version constants
#define PG_VERSION_MAX PG_VERSION_11
/***********************************************************************************************************************************
Version where various PostgreSQL capabilities were introduced
***********************************************************************************************************************************/
// application_name can be set to show the application name in pg_stat_activity
#define PG_VERSION_APPLICATION_NAME PG_VERSION_90
// pg_is_in_recovery() supported
#define PG_VERSION_HOT_STANDBY PG_VERSION_91
// pg_create_restore_point() supported
#define PG_VERSION_RESTORE_POINT PG_VERSION_91
// xlog was renamed to wal
#define PG_VERSION_WAL_RENAME PG_VERSION_10
/***********************************************************************************************************************************
PostgreSQL version string constants for use in error messages
***********************************************************************************************************************************/

View File

@ -326,7 +326,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: interface
total: 5
total: 6
coverage:
postgres/interface: full
@ -539,6 +539,20 @@ unit:
- name: info-backup-perl
total: 3
# ********************************************************************************************************************************
- name: db
test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: db
total: 2
perlReq: true
coverage:
db/db: full
db/helper: full
db/protocol: full
# ********************************************************************************************************************************
- name: command

View File

@ -64,7 +64,8 @@ harnessPqScriptRun(const char *function, const VariantList *param, HarnessPq *pa
harnessPqScriptFail = true;
THROW_FMT(
AssertError, "pq script [%u] expected function '%s' but got '%s'", harnessPqScriptIdx, result->function, function);
AssertError, "pq script [%u] expected function %s (%s) but got %s (%s)", harnessPqScriptIdx, result->function,
result->param == NULL ? "" : result->param, function, strPtr(paramStr));
}
// Check that parameters match

View File

@ -9,7 +9,11 @@ usage examples.
#ifndef HARNESS_PQ_REAL
#include <libpq-fe.h>
#include "common/macro.h"
#include "common/time.h"
#include "version.h"
/***********************************************************************************************************************************
Function constants
@ -34,6 +38,115 @@ Function constants
#define HRNPQ_SENDQUERY "PQsendQuery"
#define HRNPQ_STATUS "PQstatus"
/***********************************************************************************************************************************
Macros for defining groups of functions that implement various queries and commands
***********************************************************************************************************************************/
#define HRNPQ_MACRO_OPEN(sessionParam, connectParam) \
{.session = sessionParam, .function = HRNPQ_CONNECTDB, .param = "[\"" connectParam "\"]"}, \
{.session = sessionParam, .function = HRNPQ_STATUS, .resultInt = CONNECTION_OK}
#define HRNPQ_MACRO_SET_SEARCH_PATH(sessionParam) \
{.session = sessionParam, .function = HRNPQ_SENDQUERY, .param = "[\"set search_path = 'pg_catalog'\"]", .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_VALIDATE_QUERY(sessionParam, versionParam, pgPathParam) \
{.session = sessionParam, .function = HRNPQ_SENDQUERY, .param = \
"[\"select (select setting from pg_catalog.pg_settings where name = 'server_version_num')::int4," \
" (select setting from pg_catalog.pg_settings where name = 'data_directory')::text\"]", \
.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 = STRINGIFY(versionParam)}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = pgPathParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_SET_APPLICATION_NAME(sessionParam) \
{.session = sessionParam, .function = HRNPQ_SENDQUERY, \
.param = strPtr(strNewFmt("[\"set application_name = '" PROJECT_NAME " [%s]'\"]", cfgCommandName(cfgCommand()))), \
.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_IS_STANDBY_QUERY(sessionParam, standbyParam) \
{.session = sessionParam, .function = HRNPQ_SENDQUERY, .param = "[\"select pg_catalog.pg_is_in_recovery()\"]", .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 = STRINGIFY(standbyParam)}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_CREATE_RESTORE_POINT(sessionParam, lsnParam) \
{.session = sessionParam, \
.function = HRNPQ_SENDQUERY, .param = "[\"select pg_catalog.pg_create_restore_point('pgBackRest Archive Check')::text\"]", \
.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_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = lsnParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_WAL_SWITCH(sessionParam, walNameParam, walFileNameParam) \
{.session = sessionParam, .function = HRNPQ_SENDQUERY, \
.param = "[\"select pg_catalog.pg_" walNameParam "file_name(pg_catalog.pg_switch_" walNameParam "())::text\"]", \
.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_TEXT}, \
{.session = sessionParam, .function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = walFileNameParam}, \
{.session = sessionParam, .function = HRNPQ_CLEAR}, \
{.session = sessionParam, .function = HRNPQ_GETRESULT, .resultNull = true}
#define HRNPQ_MACRO_CLOSE(sessionParam) \
{.session = sessionParam, .function = HRNPQ_FINISH}
#define HRNPQ_MACRO_DONE() \
{.function = NULL}
/***********************************************************************************************************************************
Macros to simplify dbOpen() for specific database versions
***********************************************************************************************************************************/
#define HRNPQ_MACRO_OPEN_84(sessionParam, connectParam, pgPathParam) \
HRNPQ_MACRO_OPEN(sessionParam, connectParam), \
HRNPQ_MACRO_SET_SEARCH_PATH(sessionParam), \
HRNPQ_MACRO_VALIDATE_QUERY(sessionParam, PG_VERSION_84, pgPathParam)
#define HRNPQ_MACRO_OPEN_92(sessionParam, connectParam, pgPathParam, standbyParam) \
HRNPQ_MACRO_OPEN(sessionParam, connectParam), \
HRNPQ_MACRO_SET_SEARCH_PATH(sessionParam), \
HRNPQ_MACRO_VALIDATE_QUERY(sessionParam, PG_VERSION_92, pgPathParam), \
HRNPQ_MACRO_SET_APPLICATION_NAME(sessionParam), \
HRNPQ_MACRO_IS_STANDBY_QUERY(sessionParam, standbyParam)
/***********************************************************************************************************************************
Data type constants
***********************************************************************************************************************************/

279
test/src/module/db/dbTest.c Normal file
View File

@ -0,0 +1,279 @@
/***********************************************************************************************************************************
Test Database
***********************************************************************************************************************************/
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
#include "common/harnessLog.h"
#include "common/harnessPq.h"
#include "common/io/handleRead.h"
#include "common/io/handleWrite.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// *****************************************************************************************************************************
if (testBegin("Db and dbProtocol()"))
{
HARNESS_FORK_BEGIN()
{
HARNESS_FORK_CHILD_BEGIN(0, true)
{
IoRead *read = ioHandleReadNew(strNew("client read"), HARNESS_FORK_CHILD_READ(), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("client write"), HARNESS_FORK_CHILD_WRITE());
ioWriteOpen(write);
// Set options
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--pg1-path=/path/to/pg");
strLstAddZ(argList, "--command=backup");
strLstAddZ(argList, "--type=db");
strLstAddZ(argList, "--process=0");
strLstAddZ(argList, "remote");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
// Set script
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN(1, "dbname='postgres' port=5432"),
HRNPQ_MACRO_SET_SEARCH_PATH(1),
HRNPQ_MACRO_VALIDATE_QUERY(1, PG_VERSION_84, "/pgdata"),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_OPEN(1, "dbname='postgres' port=5432"),
HRNPQ_MACRO_SET_SEARCH_PATH(1),
HRNPQ_MACRO_VALIDATE_QUERY(1, PG_VERSION_84, "/pgdata"),
HRNPQ_MACRO_WAL_SWITCH(1, "xlog", "000000030000000200000003"),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
// Create server
ProtocolServer *server = NULL;
TEST_ASSIGN(server, protocolServerNew(strNew("db test server"), strNew("test"), read, write), "create server");
TEST_RESULT_VOID(protocolServerHandlerAdd(server, dbProtocol), "add handler");
TEST_RESULT_VOID(protocolServerProcess(server), "run process loop");
TEST_RESULT_VOID(protocolServerFree(server), "free server");
}
HARNESS_FORK_CHILD_END();
HARNESS_FORK_PARENT_BEGIN()
{
IoRead *read = ioHandleReadNew(strNew("server read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("server write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0));
ioWriteOpen(write);
// Create client
ProtocolClient *client = NULL;
Db *db = NULL;
TEST_ASSIGN(client, protocolClientNew(strNew("db test client"), strNew("test"), read, write), "create client");
// Open and free database
TEST_ASSIGN(db, dbNew(NULL, client, strNew("test")), "create db");
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
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");
TEST_RESULT_VOID(memContextCallbackClear(db->memContext), "clear context so close is not called");
TEST_RESULT_VOID(protocolClientFree(client), "free client");
}
HARNESS_FORK_PARENT_END();
}
HARNESS_FORK_END();
}
// *****************************************************************************************************************************
if (testBegin("dbGet()"))
{
DbGetResult result = {0};
// Error connecting to primary
// -------------------------------------------------------------------------------------------------------------------------
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--pg1-path=/path/to/pg");
strLstAddZ(argList, "backup");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_CONNECTDB, .param = "[\"dbname='postgres' port=5432\"]"},
{.function = HRNPQ_STATUS, .resultInt = CONNECTION_BAD},
{.function = HRNPQ_ERRORMESSAGE, .resultZ = "error"},
{.function = HRNPQ_FINISH},
{.function = NULL}
});
TEST_ERROR(dbGet(true, true), DbConnectError, "unable to find primary cluster - cannot proceed");
harnessLogResult(
"P00 WARN: unable to check pg-1: [DbConnectError] unable to connect to 'dbname='postgres' port=5432': error");
// Only cluster is a standby
// -------------------------------------------------------------------------------------------------------------------------
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN(1, "dbname='postgres' port=5432"),
HRNPQ_MACRO_SET_SEARCH_PATH(1),
HRNPQ_MACRO_VALIDATE_QUERY(1, PG_VERSION_94, "/pgdata"),
HRNPQ_MACRO_SET_APPLICATION_NAME(1),
HRNPQ_MACRO_IS_STANDBY_QUERY(1, true),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ERROR(dbGet(true, true), DbConnectError, "unable to find primary cluster - cannot proceed");
// Primary cluster found
// -------------------------------------------------------------------------------------------------------------------------
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_84(1, "dbname='postgres' port=5432", "/pgdata"),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(result, dbGet(true, true), "get primary only");
TEST_RESULT_INT(result.primaryId, 1, " check primary id");
TEST_RESULT_BOOL(result.primary != NULL, true, " check primary");
TEST_RESULT_INT(result.standbyId, 0, " check standby id");
TEST_RESULT_BOOL(result.standby == NULL, true, " check standby");
TEST_RESULT_VOID(dbFree(result.primary), "free primary");
// More than one primary found
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--pg1-path=/path/to/pg1");
strLstAddZ(argList, "--pg8-path=/path/to/pg2");
strLstAddZ(argList, "--pg8-port=5433");
strLstAddZ(argList, "backup");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_84(1, "dbname='postgres' port=5432", "/pgdata"),
HRNPQ_MACRO_OPEN_84(8, "dbname='postgres' port=5433", "/pgdata"),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_CLOSE(8),
HRNPQ_MACRO_DONE()
});
TEST_ERROR(dbGet(true, true), DbConnectError, "more than one primary cluster found");
// Two standbys found but no primary
// -------------------------------------------------------------------------------------------------------------------------
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_92(1, "dbname='postgres' port=5432", "/pgdata", true),
HRNPQ_MACRO_OPEN_92(8, "dbname='postgres' port=5433", "/pgdata", true),
HRNPQ_MACRO_CLOSE(8),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ERROR(dbGet(false, true), DbConnectError, "unable to find primary cluster - cannot proceed");
// Two standbys and primary not required
// -------------------------------------------------------------------------------------------------------------------------
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_92(1, "dbname='postgres' port=5432", "/pgdata", true),
HRNPQ_MACRO_OPEN_92(8, "dbname='postgres' port=5433", "/pgdata", true),
HRNPQ_MACRO_CLOSE(8),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(result, dbGet(false, false), "get standbys");
TEST_RESULT_INT(result.primaryId, 0, " check primary id");
TEST_RESULT_BOOL(result.primary == NULL, true, " check primary");
TEST_RESULT_INT(result.standbyId, 1, " check standby id");
TEST_RESULT_BOOL(result.standby != NULL, true, " check standby");
TEST_RESULT_VOID(dbFree(result.standby), "free standby");
// Primary and standby found
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--pg1-path=/path/to/pg1");
strLstAddZ(argList, "--pg4-path=/path/to/pg4");
strLstAddZ(argList, "--pg4-port=5433");
strLstAddZ(argList, "--pg5-host=localhost");
strLstAdd(argList, strNewFmt("--pg5-host-user=%s", testUser()));
strLstAddZ(argList, "--pg5-path=/path/to/pg5");
strLstAddZ(argList, "--pg8-path=/path/to/pg8");
strLstAddZ(argList, "--pg8-port=5434");
strLstAddZ(argList, "backup");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
harnessPqScriptSet((HarnessPq [])
{
HRNPQ_MACRO_OPEN_92(1, "dbname='postgres' port=5432", "/pgdata", true),
// pg-4 error
{.session = 4, .function = HRNPQ_CONNECTDB, .param = "[\"dbname='postgres' port=5433\"]"},
{.session = 4, .function = HRNPQ_STATUS, .resultInt = CONNECTION_BAD},
{.session = 4, .function = HRNPQ_ERRORMESSAGE, .resultZ = "error"},
{.session = 4, .function = HRNPQ_FINISH},
HRNPQ_MACRO_OPEN_92(8, "dbname='postgres' port=5434", "/pgdata", false),
HRNPQ_MACRO_CREATE_RESTORE_POINT(8, "2/3"),
HRNPQ_MACRO_WAL_SWITCH(8, "xlog", "000000010000000200000003"),
HRNPQ_MACRO_CLOSE(8),
HRNPQ_MACRO_CLOSE(1),
HRNPQ_MACRO_DONE()
});
TEST_ASSIGN(result, dbGet(false, true), "get primary and standy");
harnessLogResultRegExp(
"P00 WARN: unable to check pg-4: \\[DbConnectError\\] unable to connect to 'dbname='postgres' port=5433': error\n"
"P00 WARN: unable to check pg-5: \\[DbConnectError\\] raised from remote-0 protocol on 'localhost':"
" unable to connect to 'dbname='postgres' port=5432': could not connect to server: No such file or directory.*");
TEST_RESULT_INT(result.primaryId, 8, " check primary id");
TEST_RESULT_BOOL(result.primary != NULL, true, " check primary");
TEST_RESULT_STR(strPtr(dbWalSwitch(result.primary)), "000000010000000200000003", " wal switch");
TEST_RESULT_INT(result.standbyId, 1, " check standby id");
TEST_RESULT_BOOL(result.standby != NULL, true, " check standby");
TEST_RESULT_VOID(dbFree(result.primary), "free primary");
TEST_RESULT_VOID(dbFree(result.standby), "free standby");
}
FUNCTION_HARNESS_RESULT_VOID();
}

View File

@ -53,7 +53,15 @@ testRun(void)
#endif
PgClient *client = NULL;
TEST_ASSIGN(client, pgClientNew(NULL, 5433, strNew("postg '\\res"), NULL, 3000), "new client");
MEM_CONTEXT_TEMP_BEGIN()
{
TEST_ASSIGN(client, pgClientNew(NULL, 5433, strNew("postg '\\res"), NULL, 3000), "new client");
TEST_RESULT_VOID(pgClientMove(client, MEM_CONTEXT_OLD()), "move client");
TEST_RESULT_VOID(pgClientMove(NULL, MEM_CONTEXT_OLD()), "move null client");
}
MEM_CONTEXT_TEMP_END();
TEST_ERROR(
pgClientOpen(client), DbConnectError,
"unable to connect to 'dbname='postg \\'\\\\res' port=5433': could not connect to server: No such file or directory\n"
@ -284,6 +292,7 @@ testRun(void)
});
#endif
TEST_RESULT_VOID(pgClientClose(client), "close client");
TEST_RESULT_VOID(pgClientClose(client), "close client again");
}
FUNCTION_HARNESS_RESULT_VOID();

View File

@ -94,6 +94,13 @@ testRun(void)
TEST_RESULT_INT(info.version, PG_VERSION_83, " check version");
}
// *****************************************************************************************************************************
if (testBegin("pgWalName()"))
{
TEST_RESULT_STR(strPtr(pgWalName(PG_VERSION_96)), "xlog", "check xlog name");
TEST_RESULT_STR(strPtr(pgWalName(PG_VERSION_10)), "wal", "check wal name");
}
// *****************************************************************************************************************************
if (testBegin("pgWalFromBuffer() and pgWalFromFile()"))
{
@ -157,7 +164,6 @@ testRun(void)
"{version: 110000, systemId: 1030522662895, walSegmentSize: 16777216, pageChecksum: true}", "check log");
}
// *****************************************************************************************************************************
if (testBegin("pgWalToLog()"))
{